Files
manage/frontend/src/components/BackupJobsTable.tsx
T
Developer 77c6b62ee2 feat(frontend): slice 3 — Backups cluster migration + nav/IA
Web UI rework.
- Migrate BackupAlertsTable, BackupJobsTable, BackupRunsTable,
  BackupsPage, BackupDashboardWidget off @mui (shadcn Table + Badge
  severity variants: success=chart-2, warning=chart-3, destructive)
- App.tsx IA: Backups now top-level nav (DatabaseBackup icon);
  Media surface primary at /media; /applications -> /media redirect
  in both route trees (mirrors /monitoring -> /observability)

Gate: build + lint + test green.
2026-06-17 12:53:36 +00:00

91 lines
2.6 KiB
TypeScript

import { Badge } from "@/components/ui/badge";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import type { BackupJob, BackupRun } from "../types/backups";
interface Props {
jobs: BackupJob[];
latestRuns: Map<string, BackupRun>;
}
function formatInterval(seconds: number | null): string {
if (!seconds) return "N/A";
if (seconds < 60) return `${seconds}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
return `${Math.floor(seconds / 86400)}d`;
}
function formatTimestamp(ts: number | null): string {
if (!ts) return "Never";
return new Date(ts * 1000).toLocaleString();
}
type StatusVariant = "success" | "destructive" | "warning" | "secondary";
/**
* Map a job/run status onto a Badge variant per design §2.3:
* `success` → success (chart-2); `failure` → destructive (chart-4);
* `in_progress` → warning (chart-3); unknown → secondary (neutral accent).
*/
function statusVariant(status: string): StatusVariant {
if (status === "success") return "success";
if (status === "failure") return "destructive";
if (status === "in_progress") return "warning";
return "secondary";
}
export default function BackupJobsTable({ jobs, latestRuns }: Props) {
return (
<div className="overflow-hidden rounded-lg border border-border">
<Table aria-label="Backup jobs">
<TableHeader>
<TableRow className="bg-card hover:bg-card">
<TableHead>Name</TableHead>
<TableHead>Source</TableHead>
<TableHead>Target</TableHead>
<TableHead>Schedule</TableHead>
<TableHead>Last Status</TableHead>
<TableHead>Last Run</TableHead>
<TableHead>Next Expected</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{jobs.map((job) => {
const run = latestRuns.get(job.id);
const status = run?.status ?? "unknown";
const nextExpected =
run && job.schedule_interval_seconds
? run.started_at + job.schedule_interval_seconds
: null;
return (
<TableRow key={job.id}>
<TableCell>{job.name}</TableCell>
<TableCell>{job.source ?? "—"}</TableCell>
<TableCell>{job.target ?? "—"}</TableCell>
<TableCell>
{formatInterval(job.schedule_interval_seconds)}
</TableCell>
<TableCell>
<Badge variant={statusVariant(status)}>{status}</Badge>
</TableCell>
<TableCell>
{formatTimestamp(run?.started_at ?? null)}
</TableCell>
<TableCell>{formatTimestamp(nextExpected)}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
);
}