import { useCallback, useEffect, useMemo, useState } from "react"; import { Monitor, RefreshCw, ShieldAlert, Skull, X } from "lucide-react"; import { fetchGlobalSettings, fetchSystemStats, killVitestProcesses, updateGlobalSettings, type KillVitestResponse, type SystemStatsResponse, } from "../api"; import "./SystemStatsModal.css"; interface SystemStatsModalProps { isOpen: boolean; onClose: () => void; projectId?: string; } function formatBytes(bytes: number): string { if (!Number.isFinite(bytes) || bytes < 0) return "—"; const mb = bytes / (1024 * 1024); if (mb < 1024) return `${mb.toFixed(0)} MB`; return `${(mb / 1024).toFixed(2)} GB`; } function toPercent(used: number, total: number): string { if (!Number.isFinite(used) || !Number.isFinite(total) || total <= 0) return "—"; return `${((used / total) * 100).toFixed(1)}%`; } type Severity = "normal" | "warning" | "critical"; function heapSeverity(used: number, limit: number): Severity { if (limit <= 0) return "normal"; const pct = used / limit; if (pct >= 0.85) return "critical"; if (pct >= 0.65) return "warning"; return "normal"; } function rssSeverity(rss: number, totalSystemMem: number): Severity { if (totalSystemMem <= 0) return "normal"; const pct = rss / totalSystemMem; if (pct >= 0.5) return "critical"; if (pct >= 0.25) return "warning"; return "normal"; } function systemMemSeverity(used: number, total: number): Severity { if (total <= 0) return "normal"; const pct = used / total; if (pct >= 0.9) return "critical"; if (pct >= 0.75) return "warning"; return "normal"; } function severityClassName(severity: Severity): string { if (severity === "critical") return "system-stats-modal__value--critical"; if (severity === "warning") return "system-stats-modal__value--warning"; return ""; } function formatTimestamp(value: string | null | undefined): string { if (!value) return "Not yet"; const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) return "Not yet"; return parsed.toLocaleString(); } /** * SystemStatsModal groups dashboard runtime telemetry into five sections: * process memory metrics, CPU/load information, host memory usage, task counts * by column, and agent state counts. */ export function SystemStatsModal({ isOpen, onClose, projectId }: SystemStatsModalProps) { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [autoKillEnabled, setAutoKillEnabled] = useState(true); const [killThreshold, setKillThreshold] = useState(90); const [isKilling, setIsKilling] = useState(false); const [confirmKill, setConfirmKill] = useState(false); const [killResult, setKillResult] = useState(null); const [settingsError, setSettingsError] = useState(null); const [lastRefreshedAt, setLastRefreshedAt] = useState(null); const loadStats = useCallback(async (options?: { preserveKillResult?: boolean }) => { setLoading(true); try { const response = await fetchSystemStats(projectId); setStats(response); setError(null); setLastRefreshedAt(Date.now()); if (!options?.preserveKillResult) { setKillResult(null); } } catch (err) { setError(err instanceof Error ? err.message : "Failed to load system stats"); } finally { setLoading(false); } }, [projectId]); useEffect(() => { if (!isOpen) return; void loadStats(); const timer = window.setInterval(() => { void loadStats(); }, 5_000); return () => { window.clearInterval(timer); }; }, [isOpen, loadStats]); useEffect(() => { if (!isOpen) return; const loadSettings = async () => { try { const settings = await fetchGlobalSettings(); setAutoKillEnabled(settings.vitestAutoKillEnabled ?? true); setKillThreshold(settings.vitestKillThresholdPct ?? 90); setSettingsError(null); } catch (err) { setSettingsError(err instanceof Error ? err.message : "Failed to load vitest settings"); } }; void loadSettings(); }, [isOpen]); useEffect(() => { if (!isOpen) return; const onKeydown = (event: KeyboardEvent) => { if (event.key === "Escape") onClose(); }; document.addEventListener("keydown", onKeydown); return () => document.removeEventListener("keydown", onKeydown); }, [isOpen, onClose]); const processRows = useMemo(() => { if (!stats) return []; const system = stats.systemStats; const heapClassName = severityClassName(heapSeverity(system.heapUsed, system.heapLimit)); const rssClassName = severityClassName(rssSeverity(system.rss, system.systemTotalMem)); return [ { label: "RSS", value: formatBytes(system.rss), detail: toPercent(system.rss, system.systemTotalMem), className: rssClassName }, { label: "Heap Used", value: formatBytes(system.heapUsed), detail: `of ${formatBytes(system.heapTotal)}`, className: heapClassName }, { label: "Heap Limit", value: formatBytes(system.heapLimit), detail: "V8 limit" }, { label: "External", value: formatBytes(system.external) }, { label: "Array Buffers", value: formatBytes(system.arrayBuffers) }, ]; }, [stats]); const persistAutoKill = useCallback(async (enabled: boolean) => { setAutoKillEnabled(enabled); try { await updateGlobalSettings({ vitestAutoKillEnabled: enabled }); setSettingsError(null); } catch (err) { setSettingsError(err instanceof Error ? err.message : "Failed to save vitest settings"); } }, []); const persistKillThreshold = useCallback(async (nextThreshold: number) => { const clamped = Math.min(99, Math.max(50, Number.isFinite(nextThreshold) ? Math.round(nextThreshold) : 90)); setKillThreshold(clamped); try { await updateGlobalSettings({ vitestKillThresholdPct: clamped }); setSettingsError(null); } catch (err) { setSettingsError(err instanceof Error ? err.message : "Failed to save vitest settings"); } }, []); const handleKillVitest = useCallback(async () => { if (isKilling) return; if (!confirmKill) { setConfirmKill(true); return; } setIsKilling(true); try { const result = await killVitestProcesses(projectId); setKillResult(result); setConfirmKill(false); await loadStats({ preserveKillResult: true }); } catch (err) { setError(err instanceof Error ? err.message : "Failed to kill vitest processes"); } finally { setIsKilling(false); } }, [confirmKill, isKilling, loadStats, projectId]); if (!isOpen) return null; const system = stats?.systemStats; const isBackgroundRefreshing = loading && Boolean(stats); const refreshLabel = lastRefreshedAt ? `Updated ${new Date(lastRefreshedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", })}` : "Waiting for first update"; const taskStats = stats?.taskStats; const usedSystemMem = system ? system.systemTotalMem - system.systemFreeMem : 0; const usedSystemClassName = system ? severityClassName(systemMemSeverity(usedSystemMem, system.systemTotalMem)) : ""; const vitestProcessCount = stats?.vitestProcessCount; const killResultClassName = killResult ? killResult.killed > 0 ? "system-stats-modal__kill-result system-stats-modal__kill-result--success" : "system-stats-modal__kill-result system-stats-modal__kill-result--error" : ""; const lastAutoKillLabel = formatTimestamp(stats?.vitestLastAutoKillAt); return (
{ if (event.target === event.currentTarget) onClose(); }} data-testid="system-stats-modal-overlay" >

System Stats

Auto-refresh · 5s {refreshLabel}
{loading && !stats &&
Loading system stats…
} {error && !stats && (
{error}
)} {stats && (

Process

{processRows.map((row) => (
{row.label}
{row.value} {row.detail ? {row.detail} : null}
))}

CPU & Load

Load Avg
{system?.loadAvg.map((value) => value.toFixed(2)).join(" ") ?? "—"}
Cores
{system?.cpuCount ?? "—"}
Platform
{system?.platform ?? "—"}
Node
{system?.nodeVersion ?? "—"}
PID
{system?.pid ?? "—"}

System

Memory Used
{system ? formatBytes(usedSystemMem) : "—"} {system ? `${toPercent(usedSystemMem, system.systemTotalMem)} of ${formatBytes(system.systemTotalMem)}` : ""}
Memory Free
{system ? formatBytes(system.systemFreeMem) : "—"}

Tasks

Total
{taskStats?.total ?? 0}
{Object.entries(taskStats?.byColumn ?? {}).map(([column, count]) => (
{column}
{count}
))}

Agents

idle
{taskStats?.agents.idle ?? 0}
active
{taskStats?.agents.active ?? 0}
running
{taskStats?.agents.running ?? 0}
error
{taskStats?.agents.error ?? 0}

Vitest Controls

Vitest Processes
{vitestProcessCount ?? "—"}
{ const nextValue = Number.parseInt(event.target.value, 10); void persistKillThreshold(Number.isNaN(nextValue) ? 90 : nextValue); }} /> { const nextValue = Number.parseInt(event.target.value, 10); void persistKillThreshold(Number.isNaN(nextValue) ? 90 : nextValue); }} onBlur={() => { void persistKillThreshold(killThreshold); }} />
{killResult &&

Killed {killResult.killed} processes

}

Last auto-kill: {lastAutoKillLabel}

{settingsError &&

{settingsError}

}
)} {error && stats &&
Latest refresh failed: {error}
}
); }