Docker images / prepare-release (push) Successful in 11s
Docker images / backend-test (push) Successful in 2m4s
Docker images / frontend-image (push) Successful in 3m10s
Docker images / updater-image (push) Successful in 42s
Docker images / backend-image (push) Successful in 2m29s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 10s
Updated the BackupCreateSheet and BackupsSettings components to use checkboxes for selecting servers instead of buttons. This improves user interaction by allowing for easier selection and toggling of server states. The Checkbox component is now integrated for better accessibility and functionality.
192 lines
6.9 KiB
TypeScript
192 lines
6.9 KiB
TypeScript
"use client"
|
||
|
||
import type { Server } from "@/lib/data"
|
||
import { FormField } from "@/components/form-kit"
|
||
import { StatusBadge } from "@/components/status-badge"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Checkbox } from "@/components/ui/checkbox"
|
||
import { Input } from "@/components/ui/input"
|
||
import {
|
||
Sheet,
|
||
SheetContent,
|
||
SheetHeader,
|
||
SheetTitle,
|
||
SheetDescription,
|
||
SheetFooter,
|
||
SheetClose,
|
||
} from "@/components/ui/sheet"
|
||
import {
|
||
Stepper,
|
||
StepperContent,
|
||
StepperIndicator,
|
||
StepperItem,
|
||
StepperNav,
|
||
StepperPanel,
|
||
StepperSeparator,
|
||
StepperTitle,
|
||
StepperTrigger,
|
||
} from "@/components/reui/stepper"
|
||
import { cn } from "@/lib/utils"
|
||
|
||
export function BackupCreateSheet({
|
||
open,
|
||
onOpenChange,
|
||
step,
|
||
onStepChange,
|
||
servers,
|
||
selected,
|
||
onToggle,
|
||
onSelectAll,
|
||
onClear,
|
||
notes,
|
||
onNotesChange,
|
||
destinationLabel,
|
||
busy,
|
||
onSubmit,
|
||
}: {
|
||
open: boolean
|
||
onOpenChange: (open: boolean) => void
|
||
step: number
|
||
onStepChange: (step: number) => void
|
||
servers: Server[]
|
||
selected: Set<string>
|
||
onToggle: (id: string) => void
|
||
onSelectAll: () => void
|
||
onClear: () => void
|
||
notes: string
|
||
onNotesChange: (value: string) => void
|
||
destinationLabel: string
|
||
busy: boolean
|
||
onSubmit: () => void
|
||
}) {
|
||
return (
|
||
<Sheet open={open} onOpenChange={(v) => { onOpenChange(v); if (!v) onStepChange(1) }}>
|
||
<SheetContent side="right" className="w-full sm:max-w-md flex flex-col gap-0 p-0">
|
||
<SheetHeader className="px-6 pt-6 pb-4 border-b shrink-0">
|
||
<SheetTitle>Новый бэкап</SheetTitle>
|
||
<SheetDescription>Снять конфигурацию вручную с выбранных серверов</SheetDescription>
|
||
</SheetHeader>
|
||
|
||
<Stepper value={step} onValueChange={onStepChange} className="flex-1 flex flex-col min-h-0 px-6 py-5">
|
||
<StepperNav className="mb-5">
|
||
<StepperItem step={1}>
|
||
<StepperTrigger>
|
||
<StepperIndicator>1</StepperIndicator>
|
||
<StepperTitle className="sr-only">Серверы</StepperTitle>
|
||
</StepperTrigger>
|
||
<StepperSeparator />
|
||
</StepperItem>
|
||
<StepperItem step={2}>
|
||
<StepperTrigger>
|
||
<StepperIndicator>2</StepperIndicator>
|
||
<StepperTitle className="sr-only">Заметка</StepperTitle>
|
||
</StepperTrigger>
|
||
<StepperSeparator />
|
||
</StepperItem>
|
||
<StepperItem step={3}>
|
||
<StepperTrigger>
|
||
<StepperIndicator>3</StepperIndicator>
|
||
<StepperTitle className="sr-only">Подтверждение</StepperTitle>
|
||
</StepperTrigger>
|
||
</StepperItem>
|
||
</StepperNav>
|
||
<StepperPanel className="flex-1 overflow-y-auto">
|
||
<StepperContent value={1} className="flex flex-col gap-3">
|
||
<div className="flex items-center justify-between mb-1">
|
||
<p className="text-sm font-medium">Выберите серверы</p>
|
||
<div className="flex items-center gap-2">
|
||
<button type="button" onClick={onSelectAll} className="text-xs text-primary hover:underline">
|
||
Все
|
||
</button>
|
||
<span className="text-border">·</span>
|
||
<button
|
||
type="button"
|
||
onClick={onClear}
|
||
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
|
||
>
|
||
Сбросить
|
||
</button>
|
||
</div>
|
||
</div>
|
||
{servers.map((s) => {
|
||
const checked = selected.has(s.id)
|
||
return (
|
||
<label
|
||
key={s.id}
|
||
className={cn(
|
||
"flex cursor-pointer items-center gap-3 rounded-lg border p-3 text-left transition-colors",
|
||
checked ? "border-primary/40 bg-primary/5" : "border-border hover:bg-muted/40",
|
||
)}
|
||
>
|
||
<Checkbox
|
||
checked={checked}
|
||
onCheckedChange={() => onToggle(s.id)}
|
||
aria-label={`Выбрать ${s.name}`}
|
||
/>
|
||
<div className="flex-1 min-w-0">
|
||
<p className="text-sm font-medium">{s.name}</p>
|
||
<div className="flex items-center gap-1.5 mt-0.5">
|
||
<span className="text-xs font-mono text-muted-foreground">{s.host}</span>
|
||
<StatusBadge status={s.status} />
|
||
</div>
|
||
</div>
|
||
{s.status === "offline" && (
|
||
<span className="text-xs text-muted-foreground">недоступен</span>
|
||
)}
|
||
</label>
|
||
)
|
||
})}
|
||
</StepperContent>
|
||
<StepperContent value={2} className="flex flex-col gap-4">
|
||
<FormField label="Заметка">
|
||
<Input
|
||
placeholder="Например: перед обновлением BGP"
|
||
value={notes}
|
||
onChange={(e) => onNotesChange(e.target.value)}
|
||
/>
|
||
</FormField>
|
||
</StepperContent>
|
||
<StepperContent value={3} className="flex flex-col gap-3 text-sm">
|
||
<p className="text-muted-foreground">
|
||
Будет создан бэкап для <strong className="text-foreground">{selected.size}</strong> серверов.
|
||
</p>
|
||
<p className="text-muted-foreground">
|
||
Куда сохранится: <strong className="text-foreground">{destinationLabel}</strong>
|
||
</p>
|
||
{notes ? <p className="text-muted-foreground">Заметка: {notes}</p> : null}
|
||
</StepperContent>
|
||
</StepperPanel>
|
||
</Stepper>
|
||
|
||
<SheetFooter className="px-6 py-4 border-t shrink-0 flex-row gap-2">
|
||
<SheetClose render={<Button variant="outline" className="flex-1" />}>Отмена</SheetClose>
|
||
{step > 1 && (
|
||
<Button type="button" variant="outline" className="flex-1" onClick={() => onStepChange(step - 1)}>
|
||
Назад
|
||
</Button>
|
||
)}
|
||
{step < 3 ? (
|
||
<Button
|
||
type="button"
|
||
className="flex-1"
|
||
disabled={step === 1 && selected.size === 0}
|
||
onClick={() => onStepChange(step + 1)}
|
||
>
|
||
Далее
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
type="button"
|
||
className="flex-1"
|
||
disabled={selected.size === 0 || busy}
|
||
onClick={onSubmit}
|
||
>
|
||
Снять бэкап ({selected.size})
|
||
</Button>
|
||
)}
|
||
</SheetFooter>
|
||
</SheetContent>
|
||
</Sheet>
|
||
)
|
||
}
|