fix(backups): сохранять расписание и хранилище независимо

This commit is contained in:
Denozordec committed 2026-10-01 15:25:48 +07:00
1 parent 5ce21e9a47
commit 8e01666e0e
12 files changed
+286 -104

No files matched your search

+48 -25
View File
@@ -20,11 +20,11 @@ export type StorageProvider = "local" | "s3"
export type BackupScheduleForm = {
enabled: boolean
frequency: BackupFreq
hour: number
minute: number
weekDay: number
monthDay: number
keepCount: number
hour: number | string
minute: number | string
weekDay: number | string
monthDay: number | string
keepCount: number | string
format: "rsc" | "backup"
}
@@ -93,6 +93,13 @@ export function BackupsSettings({
onSelectAll,
onClearServers,
onSave,
onSaveStorage,
scheduleKnown = true,
storageKnown = true,
scheduleWritable = true,
storageWritable = true,
allEnabled = false,
onAllEnabledChange,
onTest,
onSync,
saveBusy,
@@ -109,6 +116,13 @@ export function BackupsSettings({
onToggleServer: (id: string) => void
onSelectAll: () => void
onClearServers: () => void
onSaveStorage: () => void
scheduleKnown?: boolean
storageKnown?: boolean
scheduleWritable?: boolean
storageWritable?: boolean
allEnabled?: boolean
onAllEnabledChange: (value: boolean) => void
onSave: () => void
onTest: () => void
onSync: () => void
@@ -121,16 +135,17 @@ export function BackupsSettings({
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
{!scheduleKnown ? <p role="status">Расписание пока неизвестно</p> : <fieldset disabled={!scheduleWritable || saveBusy || testBusy || syncBusy} className="min-w-0">
<OpsPanel title="Расписание" description="Автоматический съём конфигурации" contentClassName="px-5 py-5 flex flex-col gap-5">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-sm font-medium">Автоматический бэкап</p>
<p className="text-xs text-muted-foreground mt-0.5">Создавать бэкапы по расписанию</p>
</div>
<FormToggle checked={schedule.enabled} onChange={(v) => onScheduleChange("enabled", v)} />
<FormToggle ariaLabel="Автоматический бэкап" checked={schedule.enabled} onChange={(v) => onScheduleChange("enabled", v)} />
</div>
<div className={cn("flex flex-col gap-4", !schedule.enabled && "opacity-40 pointer-events-none")}>
<div className="flex flex-col gap-4">
<FormField label="Частота">
<SegmentedControl
value={schedule.frequency}
@@ -172,8 +187,9 @@ export function BackupsSettings({
min={1}
max={28}
className="font-mono w-24"
aria-label="День месяца"
value={schedule.monthDay}
onChange={(e) => onScheduleChange("monthDay", Math.min(28, Math.max(1, Number(e.target.value))))}
onChange={(e) => onScheduleChange("monthDay", e.target.value)}
/>
</FormField>
)}
@@ -185,10 +201,12 @@ export function BackupsSettings({
min={0}
max={23}
className="font-mono w-20 text-center"
value={String(schedule.hour).padStart(2, "0")}
onChange={(e) => onScheduleChange("hour", Math.min(23, Math.max(0, Number(e.target.value))))}
aria-label="Час запуска"
value={schedule.hour}
onChange={(e) => onScheduleChange("hour", e.target.value)}
/>
<span className="text-muted-foreground font-mono text-lg">:</span>
<Input aria-label="Минута запуска" type="number" min={0} max={59} className="font-mono w-20 text-center" value={schedule.minute} onChange={e => onScheduleChange("minute", e.target.value)} />
<div className="flex gap-1">
{[0, 15, 30, 45].map((m) => (
<button
@@ -213,26 +231,25 @@ export function BackupsSettings({
<Input
type="number"
min={1}
max={90}
max={365}
className="font-mono w-24"
aria-label="Хранить копий"
value={schedule.keepCount}
onChange={(e) => onScheduleChange("keepCount", Math.max(1, Number(e.target.value)))}
onChange={(e) => onScheduleChange("keepCount", e.target.value)}
/>
</FormField>
<div className="flex items-center justify-between gap-3 rounded-lg border border-border px-3 py-3">
<div>
<p className="text-sm font-medium">Формат файла</p>
<p className="text-xs text-muted-foreground mt-0.5">Снимается текстовый экспорт RouterOS</p>
</div>
<div className="flex items-center gap-2">
<Badge variant="secondary" size="sm">.rsc</Badge>
<Badge variant="warning-light" size="sm">.backup скоро</Badge>
</div>
</div>
<FormField label="Формат файла" hint="Плановый binary .backup пока недоступен">
<select aria-label="Формат файла" className="h-9 rounded-md border px-3 bg-background" value={schedule.format} onChange={e => onScheduleChange("format", e.target.value as "rsc" | "backup")}>
<option value="rsc">Текстовый экспорт .rsc</option>
<option value="backup" disabled>Binary .backup — недоступен</option>
</select>
</FormField>
</div>
</OpsPanel>
</fieldset>}
{!storageKnown ? <p role="status">Хранилище пока неизвестно</p> : <fieldset disabled={!storageWritable || saveBusy || testBusy || syncBusy} className="min-w-0">
<OpsPanel
title="Хранилище"
description="Локальный диск приложения или S3-compatible бакет"
@@ -353,7 +370,9 @@ export function BackupsSettings({
</div>
)}
</OpsPanel>
</fieldset>}
<fieldset disabled={!scheduleKnown || !scheduleWritable || saveBusy || testBusy || syncBusy} className="min-w-0 lg:col-span-2">
<OpsPanel
className="lg:col-span-2"
title="Серверы для бэкапа"
@@ -374,6 +393,7 @@ export function BackupsSettings({
}
contentClassName="px-5 py-5 flex flex-col gap-4"
>
<div className="flex items-center gap-2 text-sm"><Checkbox aria-label="Все включённые серверы" checked={allEnabled} onCheckedChange={value => onAllEnabledChange(value === true)} /><span>Все включённые серверы, включая добавленные позже</span></div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2">
{servers.map((s) => {
const checked = selectedServers.has(s.id)
@@ -388,6 +408,7 @@ export function BackupsSettings({
)}
>
<Checkbox
disabled={allEnabled}
checked={checked}
onCheckedChange={() => onToggleServer(s.id)}
aria-label={`Выбрать ${s.name}`}
@@ -407,12 +428,14 @@ export function BackupsSettings({
Выбрано {selectedServers.size} из {servers.length} серверов
</p>
</OpsPanel>
</fieldset>
<div className="lg:col-span-2 flex items-center gap-3">
<Button type="button" onClick={onSave} className="gap-2" disabled={saveBusy}>
<div className="lg:col-span-2 flex flex-wrap items-center gap-3">
<Button type="button" onClick={onSave} className="gap-2" disabled={!scheduleKnown || !scheduleWritable || saveBusy || testBusy || syncBusy}>
{saveBusy ? <LoaderCircleIcon className="size-4 animate-spin" /> : null}
Сохранить настройки
Сохранить расписание
</Button>
<Button type="button" onClick={onSaveStorage} disabled={!storageKnown || !storageWritable || saveBusy || testBusy || syncBusy}>Сохранить хранилище</Button>
</div>
</div>
)