"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 minute: number weekDay: number monthDay: number keepCount: number 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, form: BackupStorageForm) { if (form.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: "Connected", 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, 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 onSave: () => void onTest: () => void onSync: () => void saveBusy: boolean testBusy: boolean syncBusy: boolean }) { const status = storageStatus(savedStorage, storage) const secretPlaceholder = savedStorage?.secretConfigured ? "•••••••• (сохранён)" : "••••••••" return (

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

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

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", Math.min(28, Math.max(1, Number(e.target.value))))} /> )}
onScheduleChange("hour", Math.min(23, Math.max(0, Number(e.target.value))))} /> :
{[0, 15, 30, 45].map((m) => ( ))}
onScheduleChange("keepCount", Math.max(1, Number(e.target.value)))} />

Формат файла

Снимается текстовый экспорт RouterOS

.rsc .backup скоро
{status.label} } contentClassName="px-5 py-5 flex flex-col gap-5" > 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" >
{servers.map((s) => { const checked = selectedServers.has(s.id) return ( ) })}

Выбрано {selectedServers.size} из {servers.length} серверов

) }