"use client" import type { Server } from "@/lib/data" import type { BackupStorageSettingsDto } from "@mmapp/contracts/backups" import { Badge } from "@/components/reui/badge" import { OpsPanel } from "@/components/ops-panel" import { FormField, FormToggle, SegmentedControl } 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 { cn } from "@/lib/utils" import { LoaderCircleIcon } from "lucide-react" export const WEEK_DAYS = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"] export type BackupFreq = "daily" | "weekly" | "monthly" export type StorageProvider = "local" | "s3" export type BackupScheduleForm = { enabled: boolean frequency: BackupFreq hour: number | string minute: number | string weekDay: number | string monthDay: number | string keepCount: number | string format: "rsc" | "backup" } export type BackupStorageForm = { provider: StorageProvider s3Endpoint: string s3Region: string s3Bucket: string s3Prefix: string s3AccessKeyId: string s3SecretAccessKey: string s3ForcePathStyle: boolean keepLocalCopy: boolean showPassword: boolean } export const defaultSchedule: BackupScheduleForm = { enabled: true, frequency: "daily", hour: 3, minute: 0, weekDay: 0, monthDay: 1, keepCount: 7, format: "rsc", } export const defaultStorageForm: BackupStorageForm = { provider: "local", s3Endpoint: "", s3Region: "us-east-1", s3Bucket: "", s3Prefix: "mikrotik", s3AccessKeyId: "", s3SecretAccessKey: "", s3ForcePathStyle: true, keepLocalCopy: true, showPassword: false, } function storageStatus(saved: BackupStorageSettingsDto | null, dirty: boolean) { if (dirty) return { label: "Не сохранено", variant: "warning-light" as const } if (!saved) return { label: "Демонстрация", variant: "secondary" as const } if (saved.provider === "local") { return { label: "Локально", variant: "secondary" as const } } if (saved?.lastTestError) { return { label: "Ошибка", variant: "destructive-light" as const } } if (saved?.lastTestAt && !saved.lastTestError) { return { label: "Проверено ранее", variant: "success-light" as const } } if (saved?.secretConfigured && saved.s3Bucket) { return { label: "Не проверено", variant: "warning-light" as const } } return { label: "Не настроено", variant: "secondary" as const } } export function BackupsSettings({ schedule, onScheduleChange, storage, onStorageChange, savedStorage, servers, selectedServers, onToggleServer, onSelectAll, onClearServers, onSave, onSaveStorage, scheduleKnown = true, storageKnown = true, storageDirty = false, scheduleWritable = true, storageWritable = true, allEnabled = false, onAllEnabledChange, onTest, onSync, saveBusy, testBusy, syncBusy, }: { schedule: BackupScheduleForm onScheduleChange: (k: K, v: BackupScheduleForm[K]) => void storage: BackupStorageForm onStorageChange: (k: K, v: BackupStorageForm[K]) => void savedStorage: BackupStorageSettingsDto | null servers: Server[] selectedServers: Set onToggleServer: (id: string) => void onSelectAll: () => void onClearServers: () => void onSaveStorage: () => void scheduleKnown?: boolean storageKnown?: boolean storageDirty?: boolean scheduleWritable?: boolean storageWritable?: boolean allEnabled?: boolean onAllEnabledChange: (value: boolean) => void onSave: () => void onTest: () => void onSync: () => void saveBusy: boolean testBusy: boolean syncBusy: boolean }) { const status = storageStatus(savedStorage, storageDirty) const secretPlaceholder = savedStorage?.secretConfigured ? "•••••••• (сохранён)" : "••••••••" return (
{!scheduleKnown ?

Расписание пока неизвестно

:

Автоматический бэкап

Создавать бэкапы по расписанию

onScheduleChange("enabled", v)} />
onScheduleChange("frequency", v)} options={[ { value: "daily", label: "Ежедневно" }, { value: "weekly", label: "Еженедельно" }, { value: "monthly", label: "Ежемесячно" }, ]} /> {schedule.frequency === "weekly" && (
{WEEK_DAYS.map((d, i) => ( ))}
)} {schedule.frequency === "monthly" && ( onScheduleChange("monthDay", e.target.value)} /> )}
onScheduleChange("hour", e.target.value)} /> : onScheduleChange("minute", e.target.value)} />
{[0, 15, 30, 45].map((m) => ( ))}
onScheduleChange("keepCount", e.target.value)} />
} {!storageKnown ?

Хранилище пока неизвестно

:
{status.label} } contentClassName="px-5 py-5 flex flex-col gap-5" > {savedStorage?.lastTestAt &&

Последняя проверка: {new Date(savedStorage.lastTestAt).toLocaleString("ru-RU")}. Текущая доступность может измениться.

} onStorageChange("provider", v)} options={[ { value: "local", label: "Локально" }, { value: "s3", label: "S3" }, ]} /> {storage.provider === "local" ? (

Файлы пишутся в каталог приложения storage/backups.

) : (
onStorageChange("s3Endpoint", e.target.value)} />
onStorageChange("s3Region", e.target.value)} /> onStorageChange("s3Bucket", e.target.value)} />
onStorageChange("s3Prefix", e.target.value)} />
onStorageChange("s3AccessKeyId", e.target.value)} />
onStorageChange("s3SecretAccessKey", e.target.value)} />

Path-style

Нужен для MinIO и части совместимых API

onStorageChange("s3ForcePathStyle", v)} />

Оставлять локальную копию

После успешной загрузки в S3

onStorageChange("keepLocalCopy", v)} />
{savedStorage?.lastTestError ? (

{savedStorage.lastTestError}

) : null}
)}
}
·
} contentClassName="px-5 py-5 flex flex-col gap-4" >
onAllEnabledChange(value === true)} />Все включённые серверы, включая добавленные позже
{servers.map((s) => { const checked = selectedServers.has(s.id) return ( ) })}

{allEnabled ? "Все включённые устройства, включая добавленные позже" : "Выбрано " + selectedServers.size + " из " + servers.length + " серверов"}

) }