import { useMemo, useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Link } from "react-router"; import { toast } from "sonner"; import { Database, Plus } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; import { OnOff } from "~/components/onoff"; import type { Volume } from "~/lib/types"; import { listRepositoriesOptions, upsertBackupScheduleMutation, getBackupScheduleForVolumeOptions, } from "~/api-client/@tanstack/react-query.gen"; import { parseError } from "~/lib/errors"; import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form"; type Props = { volume: Volume; }; const getCronExpression = (frequency: string, dailyTime?: string, weeklyDay?: string): string => { if (frequency === "hourly") { return "0 * * * *"; } if (!dailyTime) { dailyTime = "02:00"; } const [hours, minutes] = dailyTime.split(":"); if (frequency === "daily") { return `${minutes} ${hours} * * *`; } return `${minutes} ${hours} * * ${weeklyDay ?? "0"}`; }; export const VolumeBackupsTabContent = ({ volume }: Props) => { const queryClient = useQueryClient(); const { data: repositoriesData, isLoading: loadingRepositories } = useQuery({ ...listRepositoriesOptions(), }); const { data: existingSchedule, isLoading: loadingSchedules } = useQuery({ ...getBackupScheduleForVolumeOptions({ path: { volumeId: volume.id.toString() } }), }); const [isEnabled, setIsEnabled] = useState(existingSchedule?.enabled ?? true); const repositories = repositoriesData || []; const selectedRepository = repositories.find((r) => r.id === (existingSchedule?.repositoryId ?? "")); const summary = useMemo(() => { const scheduleLabel = existingSchedule ? existingSchedule.cronExpression : "Every day at 02:00"; const retentionParts: string[] = []; if (existingSchedule?.retentionPolicy) { const rp = existingSchedule.retentionPolicy; if (rp.keepLast) retentionParts.push(`${rp.keepLast} last`); if (rp.keepHourly) retentionParts.push(`${rp.keepHourly} hourly`); if (rp.keepDaily) retentionParts.push(`${rp.keepDaily} daily`); if (rp.keepWeekly) retentionParts.push(`${rp.keepWeekly} weekly`); if (rp.keepMonthly) retentionParts.push(`${rp.keepMonthly} monthly`); if (rp.keepYearly) retentionParts.push(`${rp.keepYearly} yearly`); } return { vol: volume.name, scheduleLabel, repositoryLabel: selectedRepository?.name || "No repository selected", retentionLabel: retentionParts.length > 0 ? retentionParts.join(" • ") : "No retention policy", }; }, [existingSchedule, selectedRepository, volume.name]); const upsertSchedule = useMutation({ ...upsertBackupScheduleMutation(), onSuccess: () => { toast.success("Backup schedule saved successfully"); queryClient.invalidateQueries({ queryKey: ["listBackupSchedules"] }); queryClient.invalidateQueries({ queryKey: ["getBackupScheduleForVolume", volume.id.toString()] }); }, onError: (error) => { toast.error("Failed to save backup schedule", { description: parseError(error)?.message, }); }, }); const handleSubmit = (formValues: BackupScheduleFormValues) => { const cronExpression = getCronExpression(formValues.frequency, formValues.dailyTime, formValues.weeklyDay); const retentionPolicy: Record = {}; if (formValues.keepLast) retentionPolicy.keepLast = formValues.keepLast; if (formValues.keepHourly) retentionPolicy.keepHourly = formValues.keepHourly; if (formValues.keepDaily) retentionPolicy.keepDaily = formValues.keepDaily; if (formValues.keepWeekly) retentionPolicy.keepWeekly = formValues.keepWeekly; if (formValues.keepMonthly) retentionPolicy.keepMonthly = formValues.keepMonthly; if (formValues.keepYearly) retentionPolicy.keepYearly = formValues.keepYearly; upsertSchedule.mutate({ body: { volumeId: volume.id, repositoryId: formValues.repositoryId, enabled: existingSchedule ? isEnabled : true, cronExpression, retentionPolicy: Object.keys(retentionPolicy).length > 0 ? retentionPolicy : undefined, }, }); }; if (loadingRepositories || loadingSchedules) { return (

Loading...

); } if (repositories.length === 0) { return (

No repositories available

To schedule automated backups, you need to create a repository first. Repositories are secure storage locations where your backups will be stored.

); } const handleToggleEnabled = (enabled: boolean) => { if (!existingSchedule) return; setIsEnabled(enabled); upsertSchedule.mutate({ body: { volumeId: existingSchedule.volumeId, repositoryId: existingSchedule.repositoryId, enabled, cronExpression: existingSchedule.cronExpression, retentionPolicy: existingSchedule.retentionPolicy || undefined, }, }); }; return (
Schedule summary Review the backup configuration.

Volume

{summary.vol}

Schedule

{summary.scheduleLabel}

Repository

{summary.repositoryLabel}

Retention

{summary.retentionLabel}

{existingSchedule && ( <>

Last backup

{existingSchedule.lastBackupAt ? new Date(existingSchedule.lastBackupAt).toLocaleString() : "Never"}

Status

{existingSchedule.lastBackupStatus === "success" && "✓ Success"} {existingSchedule.lastBackupStatus === "error" && "✗ Error"} {!existingSchedule.lastBackupStatus && "—"}

)}
) : ( Schedule summary Review the backup configuration before saving.

Volume

{summary.vol}

Schedule

{summary.scheduleLabel}

Repository

{summary.repositoryLabel}

Retention

{summary.retentionLabel}

) } /> ); };