feat: naming backup schedules (#103)

* docs: add agents instructions

* feat: naming backup schedules

* fix: wrong table for filtering
This commit is contained in:
Nico
2025-12-04 18:31:00 +01:00
committed by GitHub
parent b8e30e298c
commit 1e20fb225e
20 changed files with 1382 additions and 434 deletions

View File

@@ -23,6 +23,7 @@ import type { BackupSchedule, Volume } from "~/client/lib/types";
import { deepClean } from "~/utils/object";
const internalFormSchema = type({
name: "1 <= string <= 32",
repositoryId: "string",
excludePatternsText: "string?",
includePatterns: "string[]?",
@@ -80,6 +81,7 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValu
const weeklyDay = frequency === "weekly" ? dayOfWeekPart : undefined;
return {
name: schedule.name,
repositoryId: schedule.repositoryId,
frequency,
dailyTime,
@@ -148,6 +150,21 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
</CardDescription>
</CardHeader>
<CardContent className="grid gap-6 md:grid-cols-2">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem className="md:col-span-2">
<FormLabel>Backup name</FormLabel>
<FormControl>
<Input placeholder="My backup" {...field} />
</FormControl>
<FormDescription>A unique name to identify this backup schedule.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="repositoryId"

View File

@@ -1,4 +1,4 @@
import { Eraser, Pencil, Play, Square, Trash2 } from "lucide-react";
import { Database, Eraser, HardDrive, Pencil, Play, Square, Trash2 } from "lucide-react";
import { useMemo, useState } from "react";
import { OnOff } from "~/client/components/onoff";
import { Button } from "~/client/components/ui/button";
@@ -18,6 +18,7 @@ import { runForgetMutation } from "~/client/api-client/@tanstack/react-query.gen
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
import { parseError } from "~/client/lib/errors";
import { Link } from "react-router";
type Props = {
schedule: BackupSchedule;
@@ -82,10 +83,17 @@ export const ScheduleSummary = (props: Props) => {
<CardHeader className="space-y-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<CardTitle>Backup schedule</CardTitle>
<CardDescription>
Automated backup configuration for volume&nbsp;
<strong className="text-strong-accent">{schedule.volume.name}</strong>
<CardTitle>{schedule.name}</CardTitle>
<CardDescription className="mt-1">
<Link to={`/volumes/${schedule.volume.name}`} className="hover:underline">
<HardDrive className="inline h-4 w-4 mr-2" />
<span>{schedule.volume.name}</span>
</Link>
<span className="mx-2"></span>
<Link to={`/repositories/${schedule.repository.name}`} className="hover:underline">
<Database className="inline h-4 w-4 mr-2 text-strong-accent" />
<span className="text-strong-accent">{schedule.repository.name}</span>
</Link>
</CardDescription>
</div>
<div className="flex items-center gap-2 justify-between sm:justify-start">

View File

@@ -35,10 +35,10 @@ import { ScheduleMirrorsConfig } from "../components/schedule-mirrors-config";
import { cn } from "~/client/lib/utils";
export const handle = {
breadcrumb: (match: Route.MetaArgs) => [
{ label: "Backups", href: "/backups" },
{ label: `Schedule #${match.params.id}` },
],
breadcrumb: (match: Route.MetaArgs) => {
const data = match.loaderData;
return [{ label: "Backups", href: "/backups" }, { label: data.schedule.name }];
},
};
export function meta(_: Route.MetaArgs) {
@@ -153,6 +153,7 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
updateSchedule.mutate({
path: { scheduleId: schedule.id.toString() },
body: {
name: formValues.name,
repositoryId: formValues.repositoryId,
enabled: schedule.enabled,
cronExpression,

View File

@@ -67,13 +67,11 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
{schedules.map((schedule) => (
<Link key={schedule.id} to={`/backups/${schedule.id}`}>
<Card key={schedule.id} className="flex flex-col h-full">
<CardHeader className="pb-3">
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-2 flex-1 min-w-0">
<HardDrive className="h-5 w-5 text-muted-foreground shrink-0" />
<CardTitle className="text-lg truncate">
Volume <span className="text-strong-accent">{schedule.volume.name}</span>
</CardTitle>
<CardHeader className="pb-3 overflow-hidden">
<div className="flex items-center justify-between gap-2 w-full">
<div className="flex items-center gap-2 flex-1 min-w-0 w-0">
<CalendarClock className="h-5 w-5 text-muted-foreground shrink-0" />
<CardTitle className="text-lg truncate">{schedule.name}</CardTitle>
</div>
<BackupStatusDot
enabled={schedule.enabled}
@@ -81,9 +79,12 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
isInProgress={schedule.lastBackupStatus === "in_progress"}
/>
</div>
<CardDescription className="flex items-center gap-2 mt-2">
<Database className="h-4 w-4" />
<span className="truncate">{schedule.repository.name}</span>
<CardDescription className="ml-0.5 flex items-center gap-2 mt-2 text-xs">
<HardDrive className="h-3.5 w-3.5" />
<span className="truncate">{schedule.volume.name}</span>
<span className="text-muted-foreground"></span>
<Database className="h-3.5 w-3.5 text-strong-accent" />
<span className="truncate text-strong-accent">{schedule.repository.name}</span>
</CardDescription>
</CardHeader>
<CardContent className="flex-1 space-y-4">

View File

@@ -83,6 +83,7 @@ export default function CreateBackup({ loaderData }: Route.ComponentProps) {
createSchedule.mutate({
body: {
name: formValues.name,
volumeId: selectedVolumeId,
repositoryId: formValues.repositoryId,
enabled: true,