import React, { useState, useEffect, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { Activity, AlertCircle, ArrowUpCircle, BarChart3, Bell, CalendarClock, CheckCircle2, Clock, Cpu, Database, Globe, HardDrive, Landmark, RefreshCw, ScrollText, Wrench, } 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'; // ─── Helpers ────────────────────────────────────────────────────────────────── 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(); } // ─── Design primitives ──────────────────────────────────────────────────────── function SectionLabel({ children }) { return (
{children}
); } function StatRow({ label, value, last }) { return (
{label} {value ?? }
); } function StatusPill({ tone = 'muted', children }) { const cls = { good: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25', warn: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-400/30', bad: 'bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25', muted: 'bg-muted/60 text-muted-foreground border-border', }[tone] ?? 'bg-muted/60 text-muted-foreground border-border'; return ( {children} ); } function StatusCard({ title, icon: Icon, status, tone = 'muted', children, className }) { const accentCls = { good: 'border-t-emerald-500/60', warn: 'border-t-amber-400/70', bad: 'border-t-red-500/60', muted: 'border-t-border/60', }[tone] ?? 'border-t-border/60'; return (
{Icon && }

{title}

{status != null && {status}}
{children}
); } function SkeletonCard() { return (
{[1, 2, 3].map(i => (
))}
); } // ─── Update Card ────────────────────────────────────────────────────────────── const CATEGORY_ORDER = ['Added', 'Changed', 'Fixed', 'Removed', 'Deprecated', 'Security']; function UpdateCard({ update, onCheckNow, checking }) { const hasUpdate = !!update.has_update; const isKnown = update.up_to_date !== null && update.up_to_date !== undefined; const hasError = !!update.error; const tone = hasUpdate ? 'warn' : isKnown && !hasError ? 'good' : 'muted'; const status = hasUpdate ? 'Update Available' : hasError ? 'Check Failed' : isKnown ? 'Up to Date' : 'Unknown'; const StatusIcon = hasUpdate ? ArrowUpCircle : hasError ? AlertCircle : CheckCircle2; const iconCls = hasUpdate ? 'text-amber-500' : hasError ? 'text-red-500' : isKnown ? 'text-emerald-500' : 'text-muted-foreground'; return (
{hasUpdate ? `v${update.latest_version} is available` : isKnown && !hasError ? 'Running the latest version' : hasError ? 'Could not reach update server' : 'Update status unknown'}
Latest {update.latest_version ? ( update.latest_release_url ? ( v{update.latest_version} ↗ ) : ( v{update.latest_version} ) ) : ( )}
{update.error && (

{update.error}

)}
); } // ─── Release Notes Card ─────────────────────────────────────────────────────── function ReleaseNotesCard({ version, historyMeta }) { if (!version) return ; const grouped = CATEGORY_ORDER.reduce((acc, cat) => { const notes = version.notes?.filter(n => n.category === cat) ?? []; if (notes.length) acc[cat] = notes; return acc; }, {}); 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 [updateData, setUpdateData] = useState(null); const [updateChecking, setUpdateChecking] = useState(false); const load = useCallback(async () => { setLoading(true); setVersion(null); setHistoryMeta(null); try { const [statusData, versionData] = await Promise.all([api.status(), api.version()]); setData(statusData); setUpdateData(statusData?.update ?? null); 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]); const handleCheckNow = useCallback(async () => { setUpdateChecking(true); try { setUpdateData(await api.checkForUpdates()); } catch (err) { toast.error(err.message || 'Update check failed'); } finally { setUpdateChecking(false); } }, []); // Normalize the nested response shape 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 bankSync = data?.bank_sync ?? data?.simplefin ?? {}; const errors = data?.errors ?? data?.recent_errors ?? []; const dbOk = db.connected ?? (db.status === 'connected') ?? true; const workerOk = !worker.enabled ? null : worker.last_error ? false : true; const notificationsConfigured = notifications.configured ?? notifications.smtp_configured ?? null; const backupsEnabled = backups.enabled ?? null; const backupsScheduled = backups.scheduled_enabled ?? null; const backupsStatus = backupsEnabled === null ? 'Pending' : !backupsEnabled ? 'Disabled' : backupsScheduled ? 'Scheduled' : 'Manual Only'; const backupsTone = backupsEnabled === null ? 'muted' : !backupsEnabled ? 'warn' : backupsScheduled ? 'good' : 'warn'; const recentErrors = Array.isArray(errors) ? errors : []; const bankSyncEnabled = bankSync.enabled ?? false; const bankSyncStatus = !bankSyncEnabled ? 'Disabled' : bankSync.running ? 'Syncing' : bankSync.last_error ? 'Error' : (bankSync.source_count ?? 0) > 0 ? 'Connected' : 'No Sources'; const bankSyncTone = !bankSyncEnabled ? 'muted' : bankSync.last_error ? 'warn' : (bankSync.source_count ?? 0) > 0 ? 'good' : 'warn'; const utcOffset = server.utc_offset != null ? `UTC${server.utc_offset >= 0 ? '+' : ''}${server.utc_offset}` : null; return (
{/* ── Page header ──────────────────────────────────────────────── */}

Server Status

Health and operational status

{/* ── Health banner ────────────────────────────────────────────── */} {!loading && data && (
{data.ok ? 'All systems operational' : 'One or more systems need attention'}
{(app.version ?? data?.version) && v{app.version ?? data?.version}} {fmtUptime(app.uptime_seconds ?? data?.uptime_seconds ?? 0)} uptime
)} {/* ── Loading skeleton ─────────────────────────────────────────── */} {loading ? ( <> {[3, 5].map((count, si) => (
0 && 'mt-8')}>
{Array.from({ length: count }).map((_, i) => )}
))} ) : ( <> {/* ── Infrastructure ─────────────────────────────────────────── */} Infrastructure
{dbOk ? : }
{/* ── Services ───────────────────────────────────────────────── */} Services
{/* ── App Health ─────────────────────────────────────────────── */} App Health
{/* ── Software ───────────────────────────────────────────────── */} Software
{updateData && ( )}
{/* ── Errors ─────────────────────────────────────────────────── */} Errors {recentErrors.length ? ( recentErrors.slice(0, 5).map((err, i) => (

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

{err.timestamp && (

{formatDateTime(err.timestamp)}

)}

{err.message ?? String(err)}

)) ) : (

No recent errors recorded.

)}
)}
); }