import { useState, useEffect, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { RefreshCw } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api'; import { cn, fmtUptime, fmtBytes } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { MarkdownText } from '@/components/MarkdownText'; // ─── Skeleton Card ──────────────────────────────────────────────────────────── function SkeletonCard() { return (
{[1, 2, 3].map((i) => (
))}
); } // ─── Stat Row ───────────────────────────────────────────────────────────────── function StatRow({ label, value, last }) { return (
{label} {value ?? '—'}
); } function formatMemory(runtime) { if (runtime.memory_used_bytes ?? runtime.memory?.used ?? runtime.memory) { return fmtBytes(runtime.memory_used_bytes ?? runtime.memory?.used ?? runtime.memory); } if (runtime.memory_mb) return `${runtime.memory_mb} MB`; return null; } function formatBytesMaybe(value) { return value === undefined || value === null ? null : fmtBytes(value); } function formatDateTime(value) { if (!value) return null; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleString(); } function StatusPill({ tone = 'muted', children }) { const cls = { good: 'bg-emerald-500/10 text-emerald-500 border-emerald-500/25', warn: 'bg-amber-500/10 text-amber-500 border-amber-500/25', bad: 'bg-red-500/10 text-red-500 border-red-500/25', muted: 'bg-muted text-muted-foreground border-border', }[tone]; return ( {children} ); } function StatusCard({ title, status, tone = 'muted', children }) { return (

{title}

{status && {status}}
{children}
); } const CATEGORY_ORDER = ['Added', 'Changed', 'Fixed', 'Removed', 'Deprecated', 'Security']; // ─── Release Notes Section ──────────────────────────────────────────────────── function ReleaseNotesSection({ version, historyMeta }) { if (!version) { return (
); } // Group notes by category, preserving CATEGORY_ORDER priority const grouped = CATEGORY_ORDER.reduce((acc, cat) => { const notes = version.notes?.filter((n) => n.category === cat) ?? []; if (notes.length) acc[cat] = notes; return acc; }, {}); // Catch any categories not in CATEGORY_ORDER version.notes?.forEach((n) => { if (!grouped[n.category]) { grouped[n.category] = [...(grouped[n.category] ?? []), n]; } }); const categories = Object.keys(grouped); const preview = categories.length ? grouped[categories[0]]?.[0]?.text : null; return (

Preview

); } // ─── StatusPage ─────────────────────────────────────────────────────────────── export default function StatusPage() { const [data, setData] = useState(null); const [version, setVersion] = useState(null); const [historyMeta, setHistoryMeta] = useState(null); const [loading, setLoading] = useState(true); const load = useCallback(async () => { setLoading(true); setVersion(null); setHistoryMeta(null); try { const [statusData, versionData] = await Promise.all([ api.status(), api.version(), ]); setData(statusData); setVersion(versionData); try { const historyData = await api.releaseHistory(); setHistoryMeta({ version: historyData.version, updated_at: historyData.updated_at, }); } catch { setHistoryMeta(null); } } catch (err) { toast.error(err.message || 'Failed to load status.'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); // Flatten nested status shape gracefully const app = data?.application ?? data?.app ?? {}; const rt = data?.runtime ?? {}; const db = data?.database ?? data?.db ?? {}; const stats = data?.statistics ?? data?.stats ?? {}; const worker = data?.worker ?? data?.jobs ?? {}; const notifications = data?.notifications ?? data?.email ?? {}; const backups = data?.backups ?? data?.backup ?? {}; const server = data?.server ?? data?.clock ?? {}; const tracker = data?.tracker ?? data?.tracker_health ?? {}; const cleanup = data?.cleanup ?? {}; const errors = data?.errors ?? data?.recent_errors ?? []; const dbOk = db.connected ?? (db.status === 'connected') ?? true; const workerOk = worker.running ?? worker.enabled ?? null; const notificationsConfigured = notifications.configured ?? notifications.smtp_configured ?? null; const backupsEnabled = backups.enabled ?? null; const recentErrors = Array.isArray(errors) ? errors : []; return (
{/* Page header — flat on background */}

Server Status

{data ? 'All systems operational' : 'Loading…'}

{/* 2x2 grid of stat cards */} {loading ? (
) : (
{/* Application */} {/* Runtime */} {/* Database */}
File path {db.file ?? db.path ?? db.filename ?? '—'}
{/* Statistics */}
)} {!loading && ( <>

Operations

{recentErrors.length ? ( recentErrors.slice(0, 4).map((err, index) => (

{err.source ?? err.type ?? 'Application'}

{err.message ?? String(err)}

)) ) : ( <> )}
)} {/* Release Notes */}
); }