refactor: dedicated edit page for backups (#736)

This commit is contained in:
Nico 2026-04-02 21:36:31 +02:00 committed by GitHub
parent a6d402b915
commit d6e80b71d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 235 additions and 69 deletions

View file

@ -16,7 +16,7 @@ import type { BackupSchedule } from "~/client/lib/types";
import { BackupProgressCard } from "./backup-progress-card"; import { BackupProgressCard } from "./backup-progress-card";
import { getBackupProgressOptions, runForgetMutation } from "~/client/api-client/@tanstack/react-query.gen"; import { getBackupProgressOptions, runForgetMutation } from "~/client/api-client/@tanstack/react-query.gen";
import { useMutation, useSuspenseQuery } from "@tanstack/react-query"; import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router"; import { Link, useNavigate } from "@tanstack/react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { handleRepositoryError } from "~/client/lib/errors"; import { handleRepositoryError } from "~/client/lib/errors";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/client/components/ui/collapsible"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/client/components/ui/collapsible";
@ -30,13 +30,12 @@ type Props = {
handleRunBackupNow: () => void; handleRunBackupNow: () => void;
handleStopBackup: () => void; handleStopBackup: () => void;
handleDeleteSchedule: () => void; handleDeleteSchedule: () => void;
setIsEditMode: (isEdit: boolean) => void;
}; };
export const ScheduleSummary = (props: Props) => { export const ScheduleSummary = (props: Props) => {
const { schedule, handleToggleEnabled, handleRunBackupNow, handleStopBackup, handleDeleteSchedule, setIsEditMode } = const { schedule, handleToggleEnabled, handleRunBackupNow, handleStopBackup, handleDeleteSchedule } = props;
props;
const { formatShortDateTime } = useTimeFormat(); const { formatShortDateTime } = useTimeFormat();
const navigate = useNavigate();
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [showForgetConfirm, setShowForgetConfirm] = useState(false); const [showForgetConfirm, setShowForgetConfirm] = useState(false);
const [showStopConfirm, setShowStopConfirm] = useState(false); const [showStopConfirm, setShowStopConfirm] = useState(false);
@ -162,7 +161,12 @@ export const ScheduleSummary = (props: Props) => {
<span>Run cleanup</span> <span>Run cleanup</span>
</Button> </Button>
)} )}
<Button variant="outline" size="sm" onClick={() => setIsEditMode(true)} className="w-full @medium:w-auto"> <Button
variant="outline"
size="sm"
onClick={() => navigate({ to: "/backups/$backupId/edit", params: { backupId: schedule.shortId } })}
className="w-full @medium:w-auto"
>
<Pencil className="h-4 w-4 mr-2" /> <Pencil className="h-4 w-4 mr-2" />
<span>Edit schedule</span> <span>Edit schedule</span>
</Button> </Button>

View file

@ -0,0 +1,87 @@
import { afterEach, expect, test, vi } from "vitest";
import { HttpResponse, http, server } from "~/test/msw/server";
import { cleanup, render, screen, userEvent } from "~/test/test-utils";
const navigateMock = vi.fn(async () => {});
vi.mock("@tanstack/react-router", async (importOriginal) => {
const actual = await importOriginal<typeof import("@tanstack/react-router")>();
return {
...actual,
useNavigate: (() => navigateMock) as typeof actual.useNavigate,
};
});
import { EditBackupPage } from "../edit-backup";
afterEach(() => {
navigateMock.mockClear();
cleanup();
});
test("submits the computed cron expression when saving a daily schedule", async () => {
const submittedBody = new Promise<Record<string, unknown>>((resolve) => {
server.use(
http.get("/api/v1/backups/:shortId", () => {
return HttpResponse.json({
shortId: "backup-1",
name: "Backup 1",
repository: { shortId: "repo-1", name: "Repo 1", type: "local" },
volume: {
id: "volume-1",
shortId: "vol-1",
name: "Volume 1",
config: { backend: "directory", path: "/mnt" },
},
cronExpression: "0 2 * * *",
retentionPolicy: null,
includePaths: ["/project"],
includePatterns: [],
excludePatterns: [],
excludeIfPresent: [],
oneFileSystem: false,
customResticParams: [],
});
}),
http.get("/api/v1/repositories", () => {
return HttpResponse.json([{ shortId: "repo-1", name: "Repo 1", type: "local" }]);
}),
http.get("/api/v1/volumes/:shortId/files", () => {
return HttpResponse.json({
files: [{ name: "project", path: "/project", type: "directory" }],
path: "/",
offset: 0,
limit: 100,
total: 1,
hasMore: false,
});
}),
http.patch("/api/v1/backups/:shortId", async ({ request }) => {
const body = (await request.json()) as Record<string, unknown>;
resolve(body);
return HttpResponse.json({
shortId: "backup-1",
volume: {
id: "volume-1",
shortId: "vol-1",
name: "Volume 1",
config: { backend: "directory", path: "/mnt" },
},
repository: { shortId: "repo-1", name: "Repo 1", type: "local" },
...body,
});
}),
);
});
render(<EditBackupPage backupId="backup-1" />, { withSuspense: true });
await userEvent.click(await screen.findByRole("button", { name: "Update schedule" }));
await expect(submittedBody).resolves.toMatchObject({
frequency: "daily",
cronExpression: "00 02 * * *",
});
});

View file

@ -1,9 +1,7 @@
import { useId, useState } from "react"; import { useState } from "react";
import { useQuery, useMutation, useSuspenseQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useSuspenseQuery, useQueryClient } from "@tanstack/react-query";
import { useSearch } from "@tanstack/react-router"; import { useSearch } from "@tanstack/react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { Save, X } from "lucide-react";
import { Button } from "~/client/components/ui/button";
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
@ -25,8 +23,6 @@ import {
deleteSnapshotMutation, deleteSnapshotMutation,
} from "~/client/api-client/@tanstack/react-query.gen"; } from "~/client/api-client/@tanstack/react-query.gen";
import { parseError, handleRepositoryError } from "~/client/lib/errors"; import { parseError, handleRepositoryError } from "~/client/lib/errors";
import { getCronExpression } from "~/utils/utils";
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
import { ScheduleSummary } from "../components/schedule-summary"; import { ScheduleSummary } from "../components/schedule-summary";
import { SnapshotFileBrowser } from "../components/snapshot-file-browser"; import { SnapshotFileBrowser } from "../components/snapshot-file-browser";
import { SnapshotTimeline } from "../components/snapshot-timeline"; import { SnapshotTimeline } from "../components/snapshot-timeline";
@ -67,8 +63,6 @@ export function ScheduleDetailsPage(props: Props) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const searchParams = useSearch({ from: "/(dashboard)/backups/$backupId/" }); const searchParams = useSearch({ from: "/(dashboard)/backups/$backupId/" });
const [isEditMode, setIsEditMode] = useState(false);
const formId = useId();
const [selectedSnapshotId, setSelectedSnapshotId] = useState<string | undefined>( const [selectedSnapshotId, setSelectedSnapshotId] = useState<string | undefined>(
initialSnapshotId ?? loaderData.snapshots?.at(-1)?.short_id, initialSnapshotId ?? loaderData.snapshots?.at(-1)?.short_id,
); );
@ -93,7 +87,6 @@ export function ScheduleDetailsPage(props: Props) {
...updateBackupScheduleMutation(), ...updateBackupScheduleMutation(),
onSuccess: () => { onSuccess: () => {
toast.success("Backup schedule saved successfully"); toast.success("Backup schedule saved successfully");
setIsEditMode(false);
}, },
onError: (error) => { onError: (error) => {
toast.error("Failed to save backup schedule", { toast.error("Failed to save backup schedule", {
@ -159,43 +152,6 @@ export function ScheduleDetailsPage(props: Props) {
}, },
}); });
const handleSubmit = (formValues: BackupScheduleFormValues) => {
if (!schedule) return;
const cronExpression = getCronExpression(
formValues.frequency,
formValues.dailyTime,
formValues.weeklyDay,
formValues.monthlyDays,
formValues.cronExpression,
);
const retentionPolicy: Record<string, number> = {};
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;
updateSchedule.mutate({
path: { shortId: schedule.shortId },
body: {
name: formValues.name,
repositoryId: formValues.repositoryId,
enabled: formValues.frequency === "manual" ? false : schedule.enabled,
cronExpression,
retentionPolicy: Object.keys(retentionPolicy).length > 0 ? retentionPolicy : undefined,
includePaths: formValues.includePaths,
includePatterns: formValues.includePatterns,
excludePatterns: formValues.excludePatterns,
excludeIfPresent: formValues.excludeIfPresent,
oneFileSystem: formValues.oneFileSystem,
customResticParams: formValues.customResticParams,
},
});
};
const handleToggleEnabled = (enabled: boolean) => { const handleToggleEnabled = (enabled: boolean) => {
updateSchedule.mutate({ updateSchedule.mutate({
path: { shortId: schedule.shortId }, path: { shortId: schedule.shortId },
@ -244,24 +200,6 @@ export function ScheduleDetailsPage(props: Props) {
}); });
}; };
if (isEditMode) {
return (
<div>
<CreateScheduleForm volume={schedule.volume} initialValues={schedule} onSubmit={handleSubmit} formId={formId} />
<div className="flex justify-end mt-4 gap-2">
<Button type="submit" className="ml-auto" variant="primary" form={formId} loading={updateSchedule.isPending}>
<Save className="h-4 w-4 mr-2" />
Update schedule
</Button>
<Button variant="outline" onClick={() => setIsEditMode(false)}>
<X className="h-4 w-4 mr-2" />
Cancel
</Button>
</div>
</div>
);
}
const selectedSnapshot = snapshots?.find((s) => s.short_id === selectedSnapshotId); const selectedSnapshot = snapshots?.find((s) => s.short_id === selectedSnapshotId);
return ( return (
@ -271,7 +209,6 @@ export function ScheduleDetailsPage(props: Props) {
handleRunBackupNow={() => runBackupNow.mutate({ path: { shortId: schedule.shortId } })} handleRunBackupNow={() => runBackupNow.mutate({ path: { shortId: schedule.shortId } })}
handleStopBackup={() => stopBackup.mutate({ path: { shortId: schedule.shortId } })} handleStopBackup={() => stopBackup.mutate({ path: { shortId: schedule.shortId } })}
handleDeleteSchedule={() => deleteSchedule.mutate({ path: { shortId: schedule.shortId } })} handleDeleteSchedule={() => deleteSchedule.mutate({ path: { shortId: schedule.shortId } })}
setIsEditMode={setIsEditMode}
schedule={schedule} schedule={schedule}
/> />
<div className={cn({ hidden: !loaderData.notifs?.length })}> <div className={cn({ hidden: !loaderData.notifs?.length })}>

View file

@ -0,0 +1,75 @@
import { useId } from "react";
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { Save, X } from "lucide-react";
import { toast } from "sonner";
import { getBackupScheduleOptions, updateBackupScheduleMutation } from "~/client/api-client/@tanstack/react-query.gen";
import { Button } from "~/client/components/ui/button";
import { parseError } from "~/client/lib/errors";
import { useNavigate } from "@tanstack/react-router";
import { getCronExpression } from "~/utils/utils";
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
export function EditBackupPage({ backupId }: { backupId: string }) {
const navigate = useNavigate();
const formId = useId();
const { data: schedule } = useSuspenseQuery({
...getBackupScheduleOptions({ path: { shortId: backupId } }),
});
const updateSchedule = useMutation({
...updateBackupScheduleMutation(),
onSuccess: () => {
toast.success("Backup schedule saved successfully");
void navigate({ to: `/backups/${schedule.shortId}` });
},
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,
formValues.monthlyDays,
formValues.cronExpression,
);
const retentionPolicy: Record<string, number> = {};
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;
updateSchedule.mutate({
path: { shortId: schedule.shortId },
body: {
...formValues,
cronExpression,
retentionPolicy: Object.keys(retentionPolicy).length > 0 ? retentionPolicy : undefined,
},
});
};
return (
<div>
<CreateScheduleForm volume={schedule.volume} initialValues={schedule} onSubmit={handleSubmit} formId={formId} />
<div className="flex justify-end mt-4 gap-2">
<Button type="submit" className="ml-auto" variant="primary" form={formId} loading={updateSchedule.isPending}>
<Save className="h-4 w-4 mr-2" />
Update schedule
</Button>
<Button variant="outline" onClick={() => navigate({ to: `/backups/${schedule.shortId}` })}>
<X className="h-4 w-4 mr-2" />
Cancel
</Button>
</div>
</div>
);
}

View file

@ -33,6 +33,7 @@ import { Route as dashboardRepositoriesRepositoryIdIndexRouteImport } from './ro
import { Route as dashboardBackupsBackupIdIndexRouteImport } from './routes/(dashboard)/backups/$backupId/index' import { Route as dashboardBackupsBackupIdIndexRouteImport } from './routes/(dashboard)/backups/$backupId/index'
import { Route as dashboardSettingsSsoNewRouteImport } from './routes/(dashboard)/settings/sso/new' import { Route as dashboardSettingsSsoNewRouteImport } from './routes/(dashboard)/settings/sso/new'
import { Route as dashboardRepositoriesRepositoryIdEditRouteImport } from './routes/(dashboard)/repositories/$repositoryId/edit' import { Route as dashboardRepositoriesRepositoryIdEditRouteImport } from './routes/(dashboard)/repositories/$repositoryId/edit'
import { Route as dashboardBackupsBackupIdEditRouteImport } from './routes/(dashboard)/backups/$backupId/edit'
import { Route as dashboardRepositoriesRepositoryIdSnapshotIdIndexRouteImport } from './routes/(dashboard)/repositories/$repositoryId/$snapshotId/index' import { Route as dashboardRepositoriesRepositoryIdSnapshotIdIndexRouteImport } from './routes/(dashboard)/repositories/$repositoryId/$snapshotId/index'
import { Route as dashboardRepositoriesRepositoryIdSnapshotIdRestoreRouteImport } from './routes/(dashboard)/repositories/$repositoryId/$snapshotId/restore' import { Route as dashboardRepositoriesRepositoryIdSnapshotIdRestoreRouteImport } from './routes/(dashboard)/repositories/$repositoryId/$snapshotId/restore'
import { Route as dashboardBackupsBackupIdSnapshotIdRestoreRouteImport } from './routes/(dashboard)/backups/$backupId/$snapshotId.restore' import { Route as dashboardBackupsBackupIdSnapshotIdRestoreRouteImport } from './routes/(dashboard)/backups/$backupId/$snapshotId.restore'
@ -164,6 +165,12 @@ const dashboardRepositoriesRepositoryIdEditRoute =
path: '/repositories/$repositoryId/edit', path: '/repositories/$repositoryId/edit',
getParentRoute: () => dashboardRouteRoute, getParentRoute: () => dashboardRouteRoute,
} as any) } as any)
const dashboardBackupsBackupIdEditRoute =
dashboardBackupsBackupIdEditRouteImport.update({
id: '/backups/$backupId/edit',
path: '/backups/$backupId/edit',
getParentRoute: () => dashboardRouteRoute,
} as any)
const dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute = const dashboardRepositoriesRepositoryIdSnapshotIdIndexRoute =
dashboardRepositoriesRepositoryIdSnapshotIdIndexRouteImport.update({ dashboardRepositoriesRepositoryIdSnapshotIdIndexRouteImport.update({
id: '/repositories/$repositoryId/$snapshotId/', id: '/repositories/$repositoryId/$snapshotId/',
@ -202,6 +209,7 @@ export interface FileRoutesByFullPath {
'/repositories/': typeof dashboardRepositoriesIndexRoute '/repositories/': typeof dashboardRepositoriesIndexRoute
'/settings/': typeof dashboardSettingsIndexRoute '/settings/': typeof dashboardSettingsIndexRoute
'/volumes/': typeof dashboardVolumesIndexRoute '/volumes/': typeof dashboardVolumesIndexRoute
'/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
'/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute '/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
'/settings/sso/new': typeof dashboardSettingsSsoNewRoute '/settings/sso/new': typeof dashboardSettingsSsoNewRoute
'/backups/$backupId/': typeof dashboardBackupsBackupIdIndexRoute '/backups/$backupId/': typeof dashboardBackupsBackupIdIndexRoute
@ -229,6 +237,7 @@ export interface FileRoutesByTo {
'/repositories': typeof dashboardRepositoriesIndexRoute '/repositories': typeof dashboardRepositoriesIndexRoute
'/settings': typeof dashboardSettingsIndexRoute '/settings': typeof dashboardSettingsIndexRoute
'/volumes': typeof dashboardVolumesIndexRoute '/volumes': typeof dashboardVolumesIndexRoute
'/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
'/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute '/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
'/settings/sso/new': typeof dashboardSettingsSsoNewRoute '/settings/sso/new': typeof dashboardSettingsSsoNewRoute
'/backups/$backupId': typeof dashboardBackupsBackupIdIndexRoute '/backups/$backupId': typeof dashboardBackupsBackupIdIndexRoute
@ -259,6 +268,7 @@ export interface FileRoutesById {
'/(dashboard)/repositories/': typeof dashboardRepositoriesIndexRoute '/(dashboard)/repositories/': typeof dashboardRepositoriesIndexRoute
'/(dashboard)/settings/': typeof dashboardSettingsIndexRoute '/(dashboard)/settings/': typeof dashboardSettingsIndexRoute
'/(dashboard)/volumes/': typeof dashboardVolumesIndexRoute '/(dashboard)/volumes/': typeof dashboardVolumesIndexRoute
'/(dashboard)/backups/$backupId/edit': typeof dashboardBackupsBackupIdEditRoute
'/(dashboard)/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute '/(dashboard)/repositories/$repositoryId/edit': typeof dashboardRepositoriesRepositoryIdEditRoute
'/(dashboard)/settings/sso/new': typeof dashboardSettingsSsoNewRoute '/(dashboard)/settings/sso/new': typeof dashboardSettingsSsoNewRoute
'/(dashboard)/backups/$backupId/': typeof dashboardBackupsBackupIdIndexRoute '/(dashboard)/backups/$backupId/': typeof dashboardBackupsBackupIdIndexRoute
@ -288,6 +298,7 @@ export interface FileRouteTypes {
| '/repositories/' | '/repositories/'
| '/settings/' | '/settings/'
| '/volumes/' | '/volumes/'
| '/backups/$backupId/edit'
| '/repositories/$repositoryId/edit' | '/repositories/$repositoryId/edit'
| '/settings/sso/new' | '/settings/sso/new'
| '/backups/$backupId/' | '/backups/$backupId/'
@ -315,6 +326,7 @@ export interface FileRouteTypes {
| '/repositories' | '/repositories'
| '/settings' | '/settings'
| '/volumes' | '/volumes'
| '/backups/$backupId/edit'
| '/repositories/$repositoryId/edit' | '/repositories/$repositoryId/edit'
| '/settings/sso/new' | '/settings/sso/new'
| '/backups/$backupId' | '/backups/$backupId'
@ -344,6 +356,7 @@ export interface FileRouteTypes {
| '/(dashboard)/repositories/' | '/(dashboard)/repositories/'
| '/(dashboard)/settings/' | '/(dashboard)/settings/'
| '/(dashboard)/volumes/' | '/(dashboard)/volumes/'
| '/(dashboard)/backups/$backupId/edit'
| '/(dashboard)/repositories/$repositoryId/edit' | '/(dashboard)/repositories/$repositoryId/edit'
| '/(dashboard)/settings/sso/new' | '/(dashboard)/settings/sso/new'
| '/(dashboard)/backups/$backupId/' | '/(dashboard)/backups/$backupId/'
@ -530,6 +543,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof dashboardRepositoriesRepositoryIdEditRouteImport preLoaderRoute: typeof dashboardRepositoriesRepositoryIdEditRouteImport
parentRoute: typeof dashboardRouteRoute parentRoute: typeof dashboardRouteRoute
} }
'/(dashboard)/backups/$backupId/edit': {
id: '/(dashboard)/backups/$backupId/edit'
path: '/backups/$backupId/edit'
fullPath: '/backups/$backupId/edit'
preLoaderRoute: typeof dashboardBackupsBackupIdEditRouteImport
parentRoute: typeof dashboardRouteRoute
}
'/(dashboard)/repositories/$repositoryId/$snapshotId/': { '/(dashboard)/repositories/$repositoryId/$snapshotId/': {
id: '/(dashboard)/repositories/$repositoryId/$snapshotId/' id: '/(dashboard)/repositories/$repositoryId/$snapshotId/'
path: '/repositories/$repositoryId/$snapshotId' path: '/repositories/$repositoryId/$snapshotId'
@ -595,6 +615,7 @@ interface dashboardRouteRouteChildren {
dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute
dashboardSettingsIndexRoute: typeof dashboardSettingsIndexRoute dashboardSettingsIndexRoute: typeof dashboardSettingsIndexRoute
dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute
dashboardBackupsBackupIdEditRoute: typeof dashboardBackupsBackupIdEditRoute
dashboardRepositoriesRepositoryIdEditRoute: typeof dashboardRepositoriesRepositoryIdEditRoute dashboardRepositoriesRepositoryIdEditRoute: typeof dashboardRepositoriesRepositoryIdEditRoute
dashboardSettingsSsoNewRoute: typeof dashboardSettingsSsoNewRoute dashboardSettingsSsoNewRoute: typeof dashboardSettingsSsoNewRoute
dashboardBackupsBackupIdIndexRoute: typeof dashboardBackupsBackupIdIndexRoute dashboardBackupsBackupIdIndexRoute: typeof dashboardBackupsBackupIdIndexRoute
@ -618,6 +639,7 @@ const dashboardRouteRouteChildren: dashboardRouteRouteChildren = {
dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute, dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute,
dashboardSettingsIndexRoute: dashboardSettingsIndexRoute, dashboardSettingsIndexRoute: dashboardSettingsIndexRoute,
dashboardVolumesIndexRoute: dashboardVolumesIndexRoute, dashboardVolumesIndexRoute: dashboardVolumesIndexRoute,
dashboardBackupsBackupIdEditRoute: dashboardBackupsBackupIdEditRoute,
dashboardRepositoriesRepositoryIdEditRoute: dashboardRepositoriesRepositoryIdEditRoute:
dashboardRepositoriesRepositoryIdEditRoute, dashboardRepositoriesRepositoryIdEditRoute,
dashboardSettingsSsoNewRoute: dashboardSettingsSsoNewRoute, dashboardSettingsSsoNewRoute: dashboardSettingsSsoNewRoute,

View file

@ -0,0 +1,41 @@
import { createFileRoute } from "@tanstack/react-router";
import { getBackupScheduleOptions, listRepositoriesOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { EditBackupPage } from "~/client/modules/backups/routes/edit-backup";
export const Route = createFileRoute("/(dashboard)/backups/$backupId/edit")({
component: RouteComponent,
errorComponent: () => <div>Failed to load backup</div>,
loader: async ({ params, context }) => {
const schedule = await context.queryClient.ensureQueryData({
...getBackupScheduleOptions({ path: { shortId: params.backupId } }),
});
await context.queryClient.ensureQueryData({
...listRepositoriesOptions(),
});
return schedule;
},
staticData: {
breadcrumb: (match) => [
{ label: "Backup Jobs", href: "/backups" },
{ label: match.loaderData?.name || "Job Details", href: `/backups/${match.params.backupId}` },
{ label: "Edit" },
],
},
head: ({ loaderData }) => ({
meta: [
{ title: `Zerobyte - Edit ${loaderData?.name || "Backup Job"}` },
{
name: "description",
content: "Edit backup job configuration and schedule.",
},
],
}),
});
function RouteComponent() {
const { backupId } = Route.useParams();
return <EditBackupPage backupId={backupId} />;
}