import { arktypeResolver } from "@hookform/resolvers/arktype"; import { useQuery } from "@tanstack/react-query"; import { type } from "arktype"; import { useCallback, useState } from "react"; import { useForm } from "react-hook-form"; import { listRepositoriesOptions } from "~/api-client/@tanstack/react-query.gen"; import { RepositoryIcon } from "~/components/repository-icon"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; import { Input } from "~/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; import { VolumeFileBrowser } from "~/components/volume-file-browser"; import type { BackupSchedule, Volume } from "~/lib/types"; import { deepClean } from "~/utils/object"; const formSchema = type({ repositoryId: "string", excludePatterns: "string[]?", includePatterns: "string[]?", frequency: "string", dailyTime: "string?", weeklyDay: "string?", keepLast: "number?", keepHourly: "number?", keepDaily: "number?", keepWeekly: "number?", keepMonthly: "number?", keepYearly: "number?", }); const cleanSchema = type.pipe((d) => formSchema(deepClean(d))); export const weeklyDays = [ { label: "Monday", value: "1" }, { label: "Tuesday", value: "2" }, { label: "Wednesday", value: "3" }, { label: "Thursday", value: "4" }, { label: "Friday", value: "5" }, { label: "Saturday", value: "6" }, { label: "Sunday", value: "0" }, ]; export type BackupScheduleFormValues = typeof formSchema.infer; type Props = { volume: Volume; initialValues?: BackupSchedule; onSubmit: (data: BackupScheduleFormValues) => void; loading?: boolean; summaryContent?: React.ReactNode; formId: string; }; const backupScheduleToFormValues = (schedule?: BackupSchedule): BackupScheduleFormValues | undefined => { if (!schedule) { return undefined; } const parts = schedule.cronExpression.split(" "); const [minutePart, hourPart, , , dayOfWeekPart] = parts; const isHourly = hourPart === "*"; const isDaily = !isHourly && dayOfWeekPart === "*"; const frequency = isHourly ? "hourly" : isDaily ? "daily" : "weekly"; const dailyTime = isHourly ? undefined : `${hourPart.padStart(2, "0")}:${minutePart.padStart(2, "0")}`; const weeklyDay = frequency === "weekly" ? dayOfWeekPart : undefined; return { repositoryId: schedule.repositoryId, frequency, dailyTime, weeklyDay, includePatterns: schedule.includePatterns || undefined, ...schedule.retentionPolicy, }; }; export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: Props) => { const form = useForm({ resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema), defaultValues: backupScheduleToFormValues(initialValues), }); const { data: repositoriesData } = useQuery({ ...listRepositoriesOptions(), }); const frequency = form.watch("frequency"); const formValues = form.watch(); const [selectedPaths, setSelectedPaths] = useState>(new Set(initialValues?.includePatterns || [])); const handleSelectionChange = useCallback( (paths: Set) => { setSelectedPaths(paths); form.setValue("includePatterns", Array.from(paths)); }, [form], ); return (
Backup automation Schedule automated backups of {volume.name} to a secure repository. ( Backup repository Choose where encrypted backups for {volume.name} will be stored. )} /> ( Backup frequency Define how often snapshots should be taken. )} /> {frequency !== "hourly" && ( ( Execution time Time of day when the backup will run. )} /> )} {frequency === "weekly" && ( ( Execution day Choose which day of the week to run the backup. )} /> )} Backup paths Select which folders to include in the backup. If no paths are selected, the entire volume will be backed up. {selectedPaths.size > 0 && (

Selected paths:

{Array.from(selectedPaths).map((path) => ( {path} ))}
)}
Retention policy Define how many snapshots to keep. Leave empty to keep all. ( Keep last N snapshots field.onChange(Number(v.target.value))} /> Keep the N most recent snapshots. )} /> ( Keep hourly field.onChange(Number(v.target.value))} /> Keep the last N hourly snapshots. )} /> ( Keep daily field.onChange(Number(v.target.value))} /> Keep the last N daily snapshots. )} /> ( Keep weekly field.onChange(Number(v.target.value))} /> Keep the last N weekly snapshots. )} /> ( Keep monthly field.onChange(Number(v.target.value))} /> Keep the last N monthly snapshots. )} /> ( Keep yearly field.onChange(Number(v.target.value))} /> Keep the last N yearly snapshots. )} />
Schedule summary Review the backup configuration.

Volume

{volume.name}

Schedule

{frequency ? frequency.charAt(0).toUpperCase() + frequency.slice(1) : "-"}

Repository

{repositoriesData?.find((r) => r.id === formValues.repositoryId)?.name || "—"}

Retention

{Object.entries(formValues) .filter(([key, value]) => key.startsWith("keep") && Boolean(value)) .map(([key, value]) => { const label = key.replace("keep", "").toLowerCase(); return `${value} ${label}`; }) .join(", ") || "-"}

); };