mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: backup details snapshots timeline & file explorer
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Database, Pencil, Play, Trash2 } from "lucide-react";
|
||||
import { Pencil, Play, Trash2 } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { listSnapshotsOptions } from "~/api-client/@tanstack/react-query.gen";
|
||||
import { ByteSize } from "~/components/bytes-size";
|
||||
import { OnOff } from "~/components/onoff";
|
||||
import { SnapshotsTable } from "~/components/snapshots-table";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import {
|
||||
@@ -16,41 +12,20 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "~/components/ui/alert-dialog";
|
||||
import type { BackupSchedule, Repository, Volume } from "~/lib/types";
|
||||
import type { BackupSchedule } from "~/lib/types";
|
||||
|
||||
type Props = {
|
||||
volume: Volume;
|
||||
schedule: BackupSchedule;
|
||||
repository: Repository;
|
||||
handleToggleEnabled: (enabled: boolean) => void;
|
||||
handleRunBackupNow: () => void;
|
||||
handleDeleteSchedule: () => void;
|
||||
setIsEditMode: (isEdit: boolean) => void;
|
||||
isDeleting?: boolean;
|
||||
};
|
||||
|
||||
export const ScheduleSummary = (props: Props) => {
|
||||
const {
|
||||
volume,
|
||||
schedule,
|
||||
repository,
|
||||
handleToggleEnabled,
|
||||
handleRunBackupNow,
|
||||
handleDeleteSchedule,
|
||||
setIsEditMode,
|
||||
isDeleting,
|
||||
} = props;
|
||||
const { schedule, handleToggleEnabled, handleRunBackupNow, handleDeleteSchedule, setIsEditMode } = props;
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
||||
const { data: snapshots, isLoading: loadingSnapshots } = useQuery({
|
||||
...listSnapshotsOptions({
|
||||
path: { name: repository.name },
|
||||
query: { volumeId: volume.id.toString() },
|
||||
}),
|
||||
refetchInterval: 10000,
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
|
||||
const summary = useMemo(() => {
|
||||
const scheduleLabel = schedule ? schedule.cronExpression : "-";
|
||||
|
||||
@@ -66,12 +41,12 @@ export const ScheduleSummary = (props: Props) => {
|
||||
}
|
||||
|
||||
return {
|
||||
vol: volume.name,
|
||||
vol: schedule.volume.name,
|
||||
scheduleLabel,
|
||||
repositoryLabel: schedule.repositoryId || "No repository selected",
|
||||
retentionLabel: retentionParts.length > 0 ? retentionParts.join(" • ") : "No retention policy",
|
||||
};
|
||||
}, [schedule, volume.name]);
|
||||
}, [schedule, schedule.volume.name]);
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
setShowDeleteConfirm(false);
|
||||
@@ -85,7 +60,8 @@ export const ScheduleSummary = (props: Props) => {
|
||||
<div>
|
||||
<CardTitle>Backup schedule</CardTitle>
|
||||
<CardDescription>
|
||||
Automated backup configuration for volume <strong className="text-strong-accent">{volume.name}</strong>
|
||||
Automated backup configuration for volume{" "}
|
||||
<strong className="text-strong-accent">{schedule.volume.name}</strong>
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -102,7 +78,6 @@ export const ScheduleSummary = (props: Props) => {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
disabled={isDeleting}
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
@@ -117,7 +92,7 @@ export const ScheduleSummary = (props: Props) => {
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase text-muted-foreground">Repository</p>
|
||||
<p className="font-medium">{repository.name}</p>
|
||||
<p className="font-medium">{schedule.repository.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase text-muted-foreground">Last backup</p>
|
||||
@@ -148,8 +123,8 @@ export const ScheduleSummary = (props: Props) => {
|
||||
<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.
|
||||
Are you sure you want to delete this backup schedule for <strong>{schedule.volume.name}</strong>? This
|
||||
action cannot be undone. Existing snapshots will not be deleted.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="flex gap-3 justify-end">
|
||||
@@ -163,59 +138,6 @@ export const ScheduleSummary = (props: Props) => {
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<Card className="p-0 gap-0">
|
||||
<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-1">
|
||||
<CardTitle>Snapshots</CardTitle>
|
||||
<CardDescription className="mt-1">
|
||||
Backup snapshots for this volume. Total: {snapshots?.snapshots.length}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
{loadingSnapshots && !snapshots ? (
|
||||
<CardContent className="flex items-center justify-center py-12">
|
||||
<p className="text-muted-foreground">Loading snapshots...</p>
|
||||
</CardContent>
|
||||
) : !snapshots ? (
|
||||
<CardContent className="flex flex-col items-center justify-center text-center py-16 px-4">
|
||||
<div className="relative mb-8">
|
||||
<div className="absolute inset-0 animate-pulse">
|
||||
<div className="w-32 h-32 rounded-full bg-primary/10 blur-2xl" />
|
||||
</div>
|
||||
<div className="relative flex items-center justify-center w-32 h-32 rounded-full bg-gradient-to-br from-primary/20 to-primary/5 border-2 border-primary/20">
|
||||
<Database className="w-16 h-16 text-primary/70" strokeWidth={1.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-md space-y-3">
|
||||
<h3 className="text-2xl font-semibold text-foreground">No snapshots yet</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Snapshots are point-in-time backups of your data. The next scheduled backup will create the first
|
||||
snapshot.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
) : (
|
||||
<>
|
||||
<SnapshotsTable snapshots={snapshots.snapshots} repositoryName={repository.name} />
|
||||
<div className="px-4 py-2 text-sm text-muted-foreground bg-card-header flex justify-between border-t">
|
||||
<span>{`Showing ${snapshots.snapshots.length} of ${snapshots.snapshots.length}`}</span>
|
||||
<span>
|
||||
Total size:
|
||||
<span className="text-strong-accent font-medium">
|
||||
<ByteSize
|
||||
bytes={snapshots.snapshots.reduce((sum, s) => sum + s.size, 0)}
|
||||
base={1024}
|
||||
maximumFractionDigits={1}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user