Files
manage/frontend/src/components/BackupRunsTable.tsx
T

104 lines
3.8 KiB
TypeScript

import {
Chip,
FormControl,
InputLabel,
MenuItem,
Paper,
Select,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@mui/material";
import { useState } from "react";
import type { BackupRun } from "../types/backups";
interface Props {
runs: BackupRun[];
}
function formatBytes(bytes: number | null): string {
if (bytes === null || bytes === undefined) return "—";
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
function formatDuration(ms: number | null): string {
if (ms === null || ms === undefined) return "—";
if (ms < 1000) return `${ms}ms`;
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
if (ms < 3600_000) return `${(ms / 60_000).toFixed(1)}m`;
return `${(ms / 3600_000).toFixed(1)}h`;
}
function formatTimestamp(ts: number): string {
return new Date(ts * 1000).toLocaleString();
}
export default function BackupRunsTable({ runs }: Props) {
const [statusFilter, setStatusFilter] = useState<string>("all");
const filteredRuns = statusFilter === "all"
? runs
: runs.filter((r) => r.status === statusFilter);
return (
<>
<FormControl sx={{ minWidth: 120, mb: 2 }}>
<InputLabel>Status</InputLabel>
<Select
value={statusFilter}
label="Status"
onChange={(e) => setStatusFilter(e.target.value)}
>
<MenuItem value="all">All</MenuItem>
<MenuItem value="success">Success</MenuItem>
<MenuItem value="failure">Failure</MenuItem>
<MenuItem value="in_progress">In Progress</MenuItem>
</Select>
</FormControl>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>Job</TableCell>
<TableCell>Status</TableCell>
<TableCell>Duration</TableCell>
<TableCell>Size</TableCell>
<TableCell>Started</TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredRuns.map((run) => (
<TableRow key={run.id} hover>
<TableCell>{run.job_id}</TableCell>
<TableCell>
<Chip
label={run.status}
color={
run.status === "success"
? "success"
: run.status === "failure"
? "error"
: "warning"
}
size="small"
/>
</TableCell>
<TableCell>{formatDuration(run.duration_ms)}</TableCell>
<TableCell>{formatBytes(run.bytes_transferred)}</TableCell>
<TableCell>{formatTimestamp(run.started_at)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</>
);
}