From 5cc7450ad02f6242cfbec42482528cf5baa59852 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Mon, 22 Dec 2025 21:18:56 +0100 Subject: [PATCH] refactor(snapshot-details): make page load faster by not awaiting restic snapshot details --- app/client/components/snapshots-table.tsx | 50 ++++------- .../components/snapshot-file-browser.tsx | 5 +- .../repositories/routes/snapshot-details.tsx | 83 ++++++++++++------- .../modules/repositories/tabs/snapshots.tsx | 5 +- 4 files changed, 75 insertions(+), 68 deletions(-) diff --git a/app/client/components/snapshots-table.tsx b/app/client/components/snapshots-table.tsx index d87d499..97a934b 100644 --- a/app/client/components/snapshots-table.tsx +++ b/app/client/components/snapshots-table.tsx @@ -1,11 +1,10 @@ import { useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { Calendar, Clock, Database, FolderTree, HardDrive, Server, Trash2 } from "lucide-react"; +import { Calendar, Clock, Database, HardDrive, Server, Trash2 } from "lucide-react"; import { Link, useNavigate } from "react-router"; import { toast } from "sonner"; import { ByteSize } from "~/client/components/bytes-size"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table"; -import { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip"; import { Button } from "~/client/components/ui/button"; import { AlertDialog, @@ -21,6 +20,7 @@ import { formatDuration } from "~/utils/utils"; import { deleteSnapshotMutation } from "~/client/api-client/@tanstack/react-query.gen"; import { parseError } from "~/client/lib/errors"; import type { BackupSchedule, Snapshot } from "../lib/types"; +import { cn } from "../lib/utils"; type Props = { snapshots: Snapshot[]; @@ -80,7 +80,6 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups }: Props) => { Size Duration Volume - Paths Actions @@ -138,39 +137,18 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups }: Props) => {
- - {backup ? ( - e.stopPropagation()} - className="text-sm hover:underline" - > - {backup.volume.name} - - ) : ( - - - )} -
-
- -
- - - - - {snapshot.paths.length} {snapshot.paths.length === 1 ? "path" : "paths"} - - - -
- {snapshot.paths.map((path) => ( -
- {path} -
- ))} -
-
-
+ + e.stopPropagation()} + className="hover:underline" + > + {backup ? backup.volume.name : "-"} + +
diff --git a/app/client/modules/backups/components/snapshot-file-browser.tsx b/app/client/modules/backups/components/snapshot-file-browser.tsx index a9f95b5..6935c8e 100644 --- a/app/client/modules/backups/components/snapshot-file-browser.tsx +++ b/app/client/modules/backups/components/snapshot-file-browser.tsx @@ -8,6 +8,7 @@ import { Button, buttonVariants } from "~/client/components/ui/button"; import type { Snapshot } from "~/client/lib/types"; import { listSnapshotFilesOptions } from "~/client/api-client/@tanstack/react-query.gen"; import { useFileBrowser } from "~/client/hooks/use-file-browser"; +import { cn } from "~/client/lib/utils"; interface Props { snapshot: Snapshot; @@ -87,7 +88,9 @@ export const SnapshotFileBrowser = (props: Props) => {
File Browser - {`Viewing snapshot from ${new Date(snapshot?.time ?? 0).toLocaleString()}`} + {`Viewing snapshot from ${new Date(snapshot?.time ?? 0).toLocaleString()}`}
[ @@ -25,15 +26,14 @@ export function meta({ params }: Route.MetaArgs) { } export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => { - const snapshot = await getSnapshotDetails({ + const snapshot = getSnapshotDetails({ path: { id: params.id, snapshotId: params.snapshotId }, }); - if (!snapshot.data) return redirect("/repositories"); const repository = await getRepository({ path: { id: params.id } }); if (!repository.data) return redirect("/repositories"); - return { snapshot: snapshot.data, repository: repository.data }; + return { snapshot: snapshot, repository: repository.data }; }; export default function SnapshotDetailsPage({ loaderData }: Route.ComponentProps) { @@ -54,9 +54,6 @@ export default function SnapshotDetailsPage({ loaderData }: Route.ComponentProps ...listBackupSchedulesOptions(), }); - const backupIds = loaderData.tags.map(Number).filter((tag) => !Number.isNaN(tag)); - const backup = schedules.data?.find((b) => backupIds.includes(b.id)); - if (!id || !snapshotId) { return (
@@ -74,7 +71,22 @@ export default function SnapshotDetailsPage({ loaderData }: Route.ComponentProps
- + + } + > + + {(value) => { + if (!value.data) return
Snapshot data not found.
; + + return ; + }} +
+
{data?.snapshot && ( @@ -99,26 +111,41 @@ export default function SnapshotDetailsPage({ loaderData }: Route.ComponentProps Time:

{new Date(data.snapshot.time).toLocaleString()}

- {backup && ( - <> -
- Backup Schedule: -

- - {backup.name} - -

-
-
- Volume: -

- - {backup.volume.name} - -

-
- - )} + Loading...}> + + {(value) => { + if (!value.data) return null; + + const backupIds = value.data.tags.map(Number).filter((tag) => !Number.isNaN(tag)); + const backupSchedule = schedules.data?.find((s) => backupIds.includes(s.id)); + + return ( + <> +
+ Backup Schedule: +

+ + {backupSchedule?.name} + +

+
+
+ Volume: +

+ + {backupSchedule?.volume.name} + +

+
+ + ); + }} +
+
+
Paths:
diff --git a/app/client/modules/repositories/tabs/snapshots.tsx b/app/client/modules/repositories/tabs/snapshots.tsx index 5814cba..ea36cba 100644 --- a/app/client/modules/repositories/tabs/snapshots.tsx +++ b/app/client/modules/repositories/tabs/snapshots.tsx @@ -29,15 +29,14 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => { if (!searchQuery) return true; const searchLower = searchQuery.toLowerCase(); - // Find the backup schedule for this snapshot const backupIds = snapshot.tags.map(Number).filter((tag) => !Number.isNaN(tag)); const backup = schedules.data?.find((b) => backupIds.includes(b.id)); return ( snapshot.short_id.toLowerCase().includes(searchLower) || snapshot.paths.some((path) => path.toLowerCase().includes(searchLower)) || - (backup?.name && backup.name.toLowerCase().includes(searchLower)) || - (backup?.volume?.name && backup.volume.name.toLowerCase().includes(searchLower)) + backup?.name?.toLowerCase().includes(searchLower) || + backup?.volume?.name?.toLowerCase().includes(searchLower) ); });