feat: delete backup schedule

This commit is contained in:
Nicolas Meienberger
2025-11-02 16:52:25 +01:00
parent 44917f3513
commit c2041932b5
2 changed files with 84 additions and 4 deletions

View File

@@ -1,12 +1,21 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Database, Pencil, Play } from "lucide-react"; import { Database, Pencil, Play, Trash2 } from "lucide-react";
import { useMemo } from "react"; import { useMemo, useState } from "react";
import { listSnapshotsOptions } from "~/api-client/@tanstack/react-query.gen"; import { listSnapshotsOptions } from "~/api-client/@tanstack/react-query.gen";
import { ByteSize } from "~/components/bytes-size"; import { ByteSize } from "~/components/bytes-size";
import { OnOff } from "~/components/onoff"; import { OnOff } from "~/components/onoff";
import { SnapshotsTable } from "~/components/snapshots-table"; import { SnapshotsTable } from "~/components/snapshots-table";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogHeader,
AlertDialogTitle,
} from "~/components/ui/alert-dialog";
import type { BackupSchedule, Repository, Volume } from "~/lib/types"; import type { BackupSchedule, Repository, Volume } from "~/lib/types";
type Props = { type Props = {
@@ -15,11 +24,23 @@ type Props = {
repository: Repository; repository: Repository;
handleToggleEnabled: (enabled: boolean) => void; handleToggleEnabled: (enabled: boolean) => void;
handleRunBackupNow: () => void; handleRunBackupNow: () => void;
handleDeleteSchedule: () => void;
setIsEditMode: (isEdit: boolean) => void; setIsEditMode: (isEdit: boolean) => void;
isDeleting?: boolean;
}; };
export const ScheduleSummary = (props: Props) => { export const ScheduleSummary = (props: Props) => {
const { volume, schedule, repository, handleToggleEnabled, handleRunBackupNow, setIsEditMode } = props; const {
volume,
schedule,
repository,
handleToggleEnabled,
handleRunBackupNow,
handleDeleteSchedule,
setIsEditMode,
isDeleting,
} = props;
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const { data: snapshots, isLoading: loadingSnapshots } = useQuery({ const { data: snapshots, isLoading: loadingSnapshots } = useQuery({
...listSnapshotsOptions({ ...listSnapshotsOptions({
@@ -52,6 +73,11 @@ export const ScheduleSummary = (props: Props) => {
}; };
}, [schedule, volume.name]); }, [schedule, volume.name]);
const handleConfirmDelete = () => {
setShowDeleteConfirm(false);
handleDeleteSchedule();
};
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Card> <Card>
@@ -72,6 +98,16 @@ export const ScheduleSummary = (props: Props) => {
<Pencil className="h-4 w-4 mr-2" /> <Pencil className="h-4 w-4 mr-2" />
Edit schedule Edit schedule
</Button> </Button>
<Button
variant="outline"
size="sm"
onClick={() => setShowDeleteConfirm(true)}
disabled={isDeleting}
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4 mr-2" />
Delete
</Button>
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <CardContent className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
@@ -107,6 +143,27 @@ export const ScheduleSummary = (props: Props) => {
</CardContent> </CardContent>
</Card> </Card>
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete backup schedule?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this backup schedule for <strong>{volume.name}</strong>? This action
cannot be undone. Existing snapshots will not be deleted.
</AlertDialogDescription>
</AlertDialogHeader>
<div className="flex gap-3 justify-end">
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirmDelete}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete schedule
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialog>
<Card className="p-0 gap-0"> <Card className="p-0 gap-0">
<CardHeader className="p-4 bg-card-header"> <CardHeader className="p-4 bg-card-header">
<div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-4 justify-between"> <div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-4 justify-between">

View File

@@ -1,6 +1,6 @@
import { useId, useState } from "react"; import { useId, useState } from "react";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
import { Link, useParams } from "react-router"; import { Link, useParams, useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card"; import { Card, CardContent } from "~/components/ui/card";
@@ -8,6 +8,7 @@ import {
upsertBackupScheduleMutation, upsertBackupScheduleMutation,
getBackupScheduleOptions, getBackupScheduleOptions,
runBackupNowMutation, runBackupNowMutation,
deleteBackupScheduleMutation,
} from "~/api-client/@tanstack/react-query.gen"; } from "~/api-client/@tanstack/react-query.gen";
import { parseError } from "~/lib/errors"; import { parseError } from "~/lib/errors";
import { getCronExpression } from "~/utils/utils"; import { getCronExpression } from "~/utils/utils";
@@ -15,6 +16,7 @@ import { CreateScheduleForm, type BackupScheduleFormValues } from "../components
import { ScheduleSummary } from "../components/schedule-summary"; import { ScheduleSummary } from "../components/schedule-summary";
export default function ScheduleDetailsPage() { export default function ScheduleDetailsPage() {
const navigate = useNavigate();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const formId = useId(); const formId = useId();
@@ -52,6 +54,19 @@ export default function ScheduleDetailsPage() {
}, },
}); });
const deleteSchedule = useMutation({
...deleteBackupScheduleMutation(),
onSuccess: () => {
toast.success("Backup schedule deleted successfully");
navigate("/backups");
},
onError: (error) => {
toast.error("Failed to delete backup schedule", {
description: parseError(error)?.message,
});
},
});
const handleSubmit = (formValues: BackupScheduleFormValues) => { const handleSubmit = (formValues: BackupScheduleFormValues) => {
if (!schedule) return; if (!schedule) return;
@@ -100,6 +115,12 @@ export default function ScheduleDetailsPage() {
}); });
}; };
const handleDeleteSchedule = () => {
if (!schedule) return;
deleteSchedule.mutate({ path: { scheduleId: schedule.id.toString() } });
};
if (loadingSchedule && !schedule) { if (loadingSchedule && !schedule) {
return ( return (
<div className="container mx-auto p-4 sm:p-8"> <div className="container mx-auto p-4 sm:p-8">
@@ -130,10 +151,12 @@ export default function ScheduleDetailsPage() {
<ScheduleSummary <ScheduleSummary
handleToggleEnabled={handleToggleEnabled} handleToggleEnabled={handleToggleEnabled}
handleRunBackupNow={handleRunBackupNow} handleRunBackupNow={handleRunBackupNow}
handleDeleteSchedule={handleDeleteSchedule}
repository={schedule.repository} repository={schedule.repository}
setIsEditMode={setIsEditMode} setIsEditMode={setIsEditMode}
schedule={schedule} schedule={schedule}
volume={schedule.volume} volume={schedule.volume}
isDeleting={deleteSchedule.isPending}
/> />
); );
} }