"use client"; import { useEffect, useState } from "react"; import { api } from "@/lib/api"; import { useLive } from "@/lib/events"; import { fmtBytes, fmtDate, fmtDuration, fmtGB, fmtMs } from "@/lib/format"; import type { SystemInfo } from "@/lib/types"; import { KV, Meter, PageHeader, Pill, Sparkline, StatTile, Tabs } from "@/components/ui"; interface Storage { total_gb: number; used_gb: number; free_gb: number; models_gb: number; logs_gb: number; database_gb: number; cache_gb: number; other_gb: number; model_root: string; min_free_gb: number; models: { id: string; name: string; disk_size_bytes: number; runtime: string; last_used_at: number | null }[] } interface Procs { workers: { model_id: string; pid: number; port: number; runtime: string; rss_gb: number; cpu_percent: number; status: string; threads: number }[]; server: { pid: number; rss_gb: number; cpu_percent: number; threads: number } } interface Hist { ts: number; mem_used_gb: number; gpu_percent: number | null; cpu_percent: number; swap_used_gb: number; worker_rss_gb: number } export default function SystemPage() { const live = useLive(); const [sys, setSys] = useState(null); const [storage, setStorage] = useState(null); const [procs, setProcs] = useState(null); const [hist, setHist] = useState([]); const [range, setRange] = useState(60); const [tab, setTab] = useState<"overview" | "storage" | "processes" | "logs">("overview"); const [audit, setAudit] = useState<{ created_at: number; actor: string; action: string; target: string | null; detail: string | null }[]>([]); const [events, setEvents] = useState<{ created_at: number; model_id: string | null; event: string; detail: string | null }[]>([]); useEffect(() => { api.get("/api/system").then(setSys).catch(() => {}); api.get<{ history: Hist[] }>(`/api/system/metrics?minutes=${range}`).then((r) => setHist(r.history)).catch(() => {}); }, [range, live.version]); useEffect(() => { if (tab === "storage") api.get("/api/system/storage").then(setStorage).catch(() => {}); if (tab === "processes") api.get("/api/system/processes").then(setProcs).catch(() => {}); if (tab === "logs") { api.get<{ logs: typeof audit }>("/api/logs/audit?limit=100").then((r) => setAudit(r.logs)); api.get<{ events: typeof events }>("/api/logs/events?limit=100").then((r) => setEvents(r.events)); } }, [tab, live.version]); const t = live.metrics || sys?.telemetry; const hw = sys?.hardware; const series = hist.length > 3 ? hist : live.metricsHistory.map((m) => ({ ts: m.ts, mem_used_gb: m.mem_used_gb, gpu_percent: m.gpu_percent, cpu_percent: m.cpu_percent, swap_used_gb: m.swap_used_gb, worker_rss_gb: m.worker_rss_gb || 0 })); return (
{tab === "overview" && t && hw && (
{t.mem_pressure_level}} sub={`${t.mem_pressure_percent}% · swap ${fmtGB(t.swap_used_gb)} / ${fmtGB(t.swap_total_gb)}`} />
History
{[15, 60, 360, 1440].map((m) => )}
Memory used (GB)
h.mem_used_gb)} max={t.mem_total_gb} height={80} unit=" GB" />
Worker memory (GB)
h.worker_rss_gb || 0)} max={t.mem_total_gb} height={80} color="var(--series-7)" unit=" GB" />
GPU utilization (%)
h.gpu_percent ?? 0)} max={100} height={80} color="var(--series-3)" unit="%" />
CPU (%)
h.cpu_percent)} max={100} height={80} color="var(--series-2)" unit="%" />
Hardware
Runtimes & policy
missing} /> not installed} /> not installed} />
)} {tab === "storage" && storage && (
{storage.used_gb.toFixed(0)} GB used of {storage.total_gb.toFixed(0)} GB{storage.free_gb.toFixed(0)} GB free · reserve {storage.min_free_gb} GB
{[["models_gb", "var(--series-1)"], ["cache_gb", "var(--series-4)"], ["logs_gb", "var(--series-2)"], ["database_gb", "var(--series-3)"], ["other_gb", "var(--color-border-strong)"]].map(([k, c]) => (
))}
Models {storage.models_gb.toFixed(1)} GB HF cache {storage.cache_gb.toFixed(1)} GB Logs {storage.logs_gb.toFixed(2)} GB Database {storage.database_gb.toFixed(2)} GB Other {storage.other_gb.toFixed(0)} GB
{storage.model_root}
{storage.models.map((m) => (location.href = `/models/${m.id}`)}>)}
ModelRuntimeSizeLast used
{m.name}{m.runtime}{fmtBytes(m.disk_size_bytes)}{m.last_used_at ? fmtDate(m.last_used_at) : "never"}
Cleanup is manual by design: delete a model from its page (confirmation required). Nothing is removed automatically.
)} {tab === "processes" && procs && (
{procs.workers.map((w) => )} {!procs.workers.length && }
ProcessPIDPortRuntimeMemoryCPUThreadsStatus
llm-api server{procs.server.pid}python{fmtGB(procs.server.rss_gb, 2)}{procs.server.cpu_percent}%{procs.server.threads}running
worker · {w.model_id}{w.pid}{w.port}{w.runtime}{fmtGB(w.rss_gb, 2)}{w.cpu_percent}%{w.threads}{w.status}
No inference worker running.
)} {tab === "logs" && (
Model events
{events.map((e, i) =>
{fmtDate(e.created_at)}{e.event}{e.model_id}{e.detail ? ` · ${e.detail}` : ""}
)}
Audit log
{audit.map((a, i) =>
{fmtDate(a.created_at)}{a.action}{a.actor} {a.target ? `→ ${a.target}` : ""} {a.detail || ""}
)}
)}
{fmtMs(0)}
); }