"use client"; import { useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@kandev/ui/card"; import { Button } from "@kandev/ui/button"; import { Badge } from "@kandev/ui/badge"; import { Spinner } from "@kandev/ui/spinner"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@kandev/ui/table"; import { IconDownload, IconTrash, IconArchive, IconRotateClockwise } from "@tabler/icons-react"; import { useBackups } from "@/hooks/domains/system/use-backups"; import { buildBackupDownloadUrl, createBackup, deleteBackup } from "@/lib/api/domains/system-api"; import { formatBytes } from "@/lib/utils/format-bytes"; import { JobProgressIndicator } from "./job-progress-indicator"; import { RestoreDialog } from "./restore-dialog"; import type { SnapshotInfo } from "@/lib/types/system"; const BACKUP_CREATE_TIMEOUT_MS = 15_000; const BACKUP_CREATE_POLL_MS = 250; function formatTimestamp(iso: string): string { if (!iso) return "-"; const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso; return d.toLocaleString(); } function BackupRow({ row, onRestore, onDelete, }: { row: SnapshotInfo; onRestore: (name: string) => void; onDelete: (name: string) => void; }) { return ( {row.name} {row.kind} {formatBytes(row.size_bytes)} {formatTimestamp(row.mtime)}
); } function BackupsList({ items, onRestore, onDelete, }: { items: SnapshotInfo[]; onRestore: (name: string) => void; onDelete: (name: string) => void; }) { return ( Name Kind Size Created Actions {items.map((row) => ( ))}
); } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } async function waitForCreatedBackup( reload: () => Promise, previousNames: Set, ): Promise { const deadline = Date.now() + BACKUP_CREATE_TIMEOUT_MS; while (Date.now() < deadline) { const items = await reload(); if (items.some((item) => item.kind === "manual" && !previousNames.has(item.name))) { return; } await sleep(BACKUP_CREATE_POLL_MS); } throw new Error("Create snapshot did not finish within 15s"); } export function BackupsTable() { const { backups, loaded, isLoading, reload } = useBackups(); const [creating, setCreating] = useState(false); const [restoreName, setRestoreName] = useState(null); const [error, setError] = useState(null); const onCreate = async () => { setCreating(true); setError(null); const previousNames = new Set(backups.map((backup) => backup.name)); try { await createBackup(); await waitForCreatedBackup(reload, previousNames); } catch (err) { setError(err instanceof Error ? err.message : "Create snapshot failed"); } finally { setCreating(false); } }; const onDelete = async (name: string) => { setError(null); try { await deleteBackup(name); await reload(); } catch (err) { setError(err instanceof Error ? err.message : "Delete failed"); } }; const items = backups; return ( Backups

A backup is a full copy of Kandev's database (tasks, sessions, settings). Kandev automatically creates one before every version upgrade so you can roll back if something goes wrong. You can also create one manually before risky operations like factory reset. Only the latest 2 backups are kept on disk.

{error && (

{error}

)} {!loaded && isLoading && (
Loading backups...
)} {loaded && items.length === 0 && (

No backups yet. Snapshots created automatically on version upgrade or manually via the button above will appear here.

)} {items.length > 0 && ( setRestoreName(n)} onDelete={(n) => void onDelete(n)} /> )}
{ if (!open) setRestoreName(null); }} name={restoreName ?? ""} />
); }