/** * SystemStatusDialog — live health dashboard for HomePilot services. * * Shows a donut chart (pure CSS conic-gradient), service health cards, * and architecture overview. No chart library needed. */ import React, { useEffect, useMemo, useState } from 'react' import { X, Activity, Database, Cpu, Server, Bot, PlugZap } from 'lucide-react' import { fetchSystemOverview, type SystemOverviewResponse } from './systemApi' import SystemResourcesCard from './SystemResourcesCard' /* ── helpers ───────────────────────────────────────────── */ function formatUptime(sec: number) { const h = Math.floor(sec / 3600) const m = Math.floor((sec % 3600) / 60) return `${h}h ${m}m` } function statusTone(ok?: boolean) { if (ok) return 'text-emerald-300/90 border-emerald-500/15 bg-emerald-500/8' return 'text-red-300/80 border-red-400/15 bg-red-500/6' } const SERVICE_LABELS: Record = { backend: 'Backend', ollama: 'Ollama', avatar_svc: 'Avatar Svc', comfyui: 'ComfyUI', forge: 'ContextForge', sqlite: 'SQLite', } /* ── main component ────────────────────────────────────── */ export default function SystemStatusDialog({ backendUrl, apiKey, onClose, }: { backendUrl: string apiKey?: string onClose: () => void }) { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) /* Escape to close */ useEffect(() => { const onEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', onEsc) return () => document.removeEventListener('keydown', onEsc) }, [onClose]) /* Fetch data */ useEffect(() => { let mounted = true ;(async () => { try { setLoading(true) const result = await fetchSystemOverview(backendUrl, apiKey) if (mounted) setData(result) } catch (e: any) { if (mounted) setError(e?.message || 'Failed to load system status') } finally { if (mounted) setLoading(false) } })() return () => { mounted = false } }, [backendUrl, apiKey]) /* Donut gradient — glow only on the green arc */ const healthyPct = data ? Math.round((data.overview.healthy_services / Math.max(data.overview.total_services, 1)) * 100) : 0 const donut = useMemo(() => { if (!data) return 'conic-gradient(#374151 0% 100%)' return `conic-gradient(#34d399 0% ${healthyPct}%, rgba(255,255,255,0.06) ${healthyPct}% 100%)` }, [data, healthyPct]) const handleBackdrop = (e: React.MouseEvent) => { if (e.target === e.currentTarget) onClose() } return (
{/* Close — smaller, subtler, brighter on hover */} {/* Accent bar */}
{/* Header */}
System Overview

HomePilot Runtime

Live health dashboard for your backend, models, and services.

{data && (
v{data.overview.version}
)}
{loading ? (
Loading system status...
) : error ? (
{error}
) : data ? ( <> {/* Top metrics — dimmer labels, more spacing */}
{/* Machine Capacity — additive, fails gracefully */} {/* Donut + Architecture */}
{/* Donut chart — flat card, no nested box */}
Service Stability
{/* Donut: thinner stroke, more center room */}
{/* Green arc glow (behind the donut) */}
{/* Donut track */}
{/* Thick cutout — thinner ring (inset-[14px] → thinner stroke) */}
{/* Center text */}
{healthyPct}%
healthy
{data.overview.healthy_services} of {data.overview.total_services} online
{/* Legend — smaller, dimmer, more spacing from donut */}
{/* Architecture flow — more gap, padding, dimmer descriptions */}
Architecture Flow
} items={[`${data.architecture.inputs.virtual_servers_active}/${data.architecture.inputs.virtual_servers_total} virtual servers`]} /> } items={[`ContextForge ${data.architecture.gateway.contextforge_ok ? 'online' : 'offline'}`]} /> } items={[data.architecture.infrastructure.database, data.architecture.infrastructure.memory_mode]} /> } items={[ `${data.architecture.outputs.mcp_servers_active}/${data.architecture.outputs.mcp_servers_total} MCP`, `${data.architecture.outputs.a2a_agents_active}/${data.architecture.outputs.a2a_agents_total} A2A`, `${data.architecture.outputs.tools_active}/${data.architecture.outputs.tools_total} Tools`, ]} />
{/* Services + Inventory */}
Services
{Object.entries(data.services).map(([key, svc]) => (
{key === 'sqlite' ? : key === 'backend' ? : key === 'avatar_svc' ? : }
{SERVICE_LABELS[key] || key}
{svc.url || svc.base_url || svc.service || 'internal'}
{svc.latency_ms != null ? `${svc.latency_ms}ms` : '\u2014'}
{svc.ok ? 'Healthy' : 'Offline'}
))}
Inventory
) : null}
) } /* ── sub-components ────────────────────────────────────── */ function MetricCard({ label, value, delay = 0 }: { label: string; value: string; delay?: number }) { return (
{label}
{value}
) } function SmallCount({ label, value, sub }: { label: string; value: number; sub: string }) { return (
{label}
{value}
{sub}
) } function Legend({ color, label }: { color: string; label: string }) { return (
{label}
) } function FlowCard({ title, icon, items }: { title: string; icon: React.ReactNode; items: string[] }) { return (
{icon} {title}
{items.map((item) => (
{item}
))}
) }