From 18115b374c873fd399e628363df0f2586b270171 Mon Sep 17 00:00:00 2001
From: Nicolas Meienberger
Date: Sat, 1 Nov 2025 17:09:43 +0100
Subject: [PATCH] feat(frontend): backup jobs page
---
apps/client/app/api-client/types.gen.ts | 72 ++++++-
apps/client/app/components/app-sidebar.tsx | 7 +-
.../app/components/create-repository-form.tsx | 17 --
apps/client/app/components/ui/badge.tsx | 36 ++++
apps/client/app/lib/breadcrumbs.ts | 17 ++
.../app/modules/details/tabs/backups.tsx | 4 +-
.../components/restore-snapshot-dialog.tsx | 1 -
apps/client/app/routes.ts | 2 +
apps/client/app/routes/backup-jobs.tsx | 112 +++++++++++
apps/client/app/routes/schedule-details.tsx | 175 ++++++++++++++++++
apps/server/src/db/schema.ts | 30 ++-
.../server/src/modules/backups/backups.dto.ts | 9 +-
.../src/modules/backups/backups.service.ts | 4 +
.../modules/repositories/repositories.dto.ts | 2 +-
apps/server/src/modules/volumes/volume.dto.ts | 2 +-
apps/server/src/utils/restic.ts | 2 +-
docker-compose.yml | 1 +
openapi-ts.config.ts | 2 +-
packages/schemas/src/restic.ts | 2 +-
19 files changed, 459 insertions(+), 38 deletions(-)
create mode 100644 apps/client/app/components/ui/badge.tsx
create mode 100644 apps/client/app/routes/backup-jobs.tsx
create mode 100644 apps/client/app/routes/schedule-details.tsx
diff --git a/apps/client/app/api-client/types.gen.ts b/apps/client/app/api-client/types.gen.ts
index 7702039..707e063 100644
--- a/apps/client/app/api-client/types.gen.ts
+++ b/apps/client/app/api-client/types.gen.ts
@@ -670,7 +670,7 @@ export type ListRepositoriesResponses = {
}
| {
backend: "local";
- path: string;
+ name: string;
};
createdAt: number;
id: string;
@@ -697,7 +697,7 @@ export type CreateRepositoryData = {
}
| {
backend: "local";
- path: string;
+ name: string;
};
name: string;
compressionMode?: "auto" | "better" | "fastest" | "max" | "off";
@@ -767,7 +767,7 @@ export type GetRepositoryResponses = {
}
| {
backend: "local";
- path: string;
+ name: string;
};
createdAt: number;
id: string;
@@ -856,7 +856,6 @@ export type RestoreSnapshotData = {
snapshotId: string;
exclude?: Array;
include?: Array;
- path?: string;
};
path: {
name: string;
@@ -1018,6 +1017,29 @@ export type GetBackupScheduleResponses = {
lastBackupError: string | null;
lastBackupStatus: "error" | "success" | null;
nextBackupAt: number | null;
+ repository: {
+ compressionMode: "auto" | "better" | "fastest" | "max" | "off" | null;
+ config:
+ | {
+ accessKeyId: string;
+ backend: "s3";
+ bucket: string;
+ endpoint: string;
+ secretAccessKey: string;
+ }
+ | {
+ backend: "local";
+ name: string;
+ };
+ createdAt: number;
+ id: string;
+ lastChecked: number | null;
+ lastError: string | null;
+ name: string;
+ status: "error" | "healthy" | "unknown" | null;
+ type: "local" | "s3";
+ updatedAt: number;
+ };
repositoryId: string;
retentionPolicy: {
keepDaily?: number;
@@ -1029,6 +1051,48 @@ export type GetBackupScheduleResponses = {
keepYearly?: number;
} | null;
updatedAt: number;
+ volume: {
+ autoRemount: boolean;
+ config:
+ | {
+ backend: "directory";
+ }
+ | {
+ backend: "nfs";
+ exportPath: string;
+ server: string;
+ version: "3" | "4" | "4.1";
+ port?: number;
+ }
+ | {
+ backend: "smb";
+ password: string;
+ server: string;
+ share: string;
+ username: string;
+ vers?: "1.0" | "2.0" | "2.1" | "3.0";
+ port?: number;
+ domain?: string;
+ }
+ | {
+ backend: "webdav";
+ path: string;
+ server: string;
+ port?: number;
+ password?: string;
+ ssl?: boolean;
+ username?: string;
+ };
+ createdAt: number;
+ id: number;
+ lastError: string | null;
+ lastHealthCheck: number;
+ name: string;
+ path: string;
+ status: "error" | "mounted" | "unmounted";
+ type: "directory" | "nfs" | "smb" | "webdav";
+ updatedAt: number;
+ };
volumeId: number;
};
};
diff --git a/apps/client/app/components/app-sidebar.tsx b/apps/client/app/components/app-sidebar.tsx
index d0fbd5b..7107db4 100644
--- a/apps/client/app/components/app-sidebar.tsx
+++ b/apps/client/app/components/app-sidebar.tsx
@@ -1,4 +1,4 @@
-import { Database, HardDrive, Mountain } from "lucide-react";
+import { CalendarClock, Database, HardDrive, Mountain } from "lucide-react";
import { Link, NavLink } from "react-router";
import {
Sidebar,
@@ -25,6 +25,11 @@ const items = [
url: "/repositories",
icon: Database,
},
+ {
+ title: "Backup jobs",
+ url: "/backup-jobs",
+ icon: CalendarClock,
+ },
];
export function AppSidebar() {
diff --git a/apps/client/app/components/create-repository-form.tsx b/apps/client/app/components/create-repository-form.tsx
index 25c472f..3153af1 100644
--- a/apps/client/app/components/create-repository-form.tsx
+++ b/apps/client/app/components/create-repository-form.tsx
@@ -134,23 +134,6 @@ export const CreateRepositoryForm = ({
)}
/>
- {watchedBackend === "local" && (
- (
-
- Path
-
-
-
- Local filesystem path where the repository will be stored.
-
-
- )}
- />
- )}
-
{watchedBackend === "s3" && (
<>
svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span";
+
+ return ;
+}
+
+export { Badge, badgeVariants };
diff --git a/apps/client/app/lib/breadcrumbs.ts b/apps/client/app/lib/breadcrumbs.ts
index 8c94f25..d3b3e7d 100644
--- a/apps/client/app/lib/breadcrumbs.ts
+++ b/apps/client/app/lib/breadcrumbs.ts
@@ -42,6 +42,23 @@ export function generateBreadcrumbs(pathname: string, params: Record {
To schedule automated backups, you need to create a repository first. Repositories are secure storage
locations where your backups will be stored.
-