import { cn } from "../lib/cn"; import { formatBytes } from "../lib/format"; import type { MetricSample } from "../types/machines"; import { useOpenGeniText } from "../messages/runtime"; export type MachineMetricsProps = { /** The latest metric sample, or null when the machine hasn't reported yet. */ metrics: MetricSample | null; /** Compact (dashboard row) vs full (the dock detail). */ density?: "compact" | "full" | undefined; className?: string | undefined; }; /** Clamp a 0..100-ish percent into the bar range. */ function pct(value: number): number { if (!Number.isFinite(value)) return 0; return Math.max(0, Math.min(100, value)); } /** A label/value pair with a token-tinted utilisation bar. */ function Meter({ label, value, fillPct, tone, }: { label: string; value: string; fillPct: number; tone: "ok" | "warn" | "hot"; }) { // Traffic-light ramp, aligned with the fused health verdict: calm resources // read green (idle), pressure ambers (running), near-the-wall reds (failed). const fillClass = tone === "hot" ? "bg-og-status-failed" : tone === "warn" ? "bg-og-status-running" : "bg-og-status-idle"; return (
{label} {value}
); } /** Map a percent to a calm/warn/hot tone (no color invented — token classes). */ function toneFor(p: number): "ok" | "warn" | "hot" { if (p >= 90) return "hot"; if (p >= 70) return "warn"; return "ok"; } /** * Load average as three labeled mini-stats (1m / 5m / 15m) — an honest triple of * numbers, NOT a fake gauge. Load is not a 0..100 ratio and we don't know the * core count, so a raw load of 2 or 8 is not inherently "bad" — we render it * NEUTRAL rather than falsely alarming. The real saturation signal is the run * queue, which rides alongside as a quiet chip when there's pending work. */ function StatTriple({ load1, load5, load15, runQueue, }: { load1: number; load5: number; load15: number; runQueue: number; }) { const t = useOpenGeniText(); const stats: Array<{ label: string; value: number }> = [ { label: "1m", value: load1 }, { label: "5m", value: load5 }, { label: "15m", value: load15 }, ]; return (
{t("machineMetrics.load")}
{stats.map((s) => (
{s.label} {s.value.toFixed(2)}
))}
{runQueue > 0 ? (
{t("machineMetrics.queue")} {runQueue}
) : null}
); } /** * Per-machine resource meters: CPU%, load average, memory, disk, and GPU when * present. Renders the M2 `machine_metrics_latest` sample. When `metrics` is * null the panel shows a quiet "no samples yet" placeholder (offline / just * enrolled). GPU rows only render when `gpuUtilPct` is non-null. */ export function MachineMetrics({ metrics, density = "compact", className }: MachineMetricsProps) { const t = useOpenGeniText(); if (!metrics) { return (
{t("machineMetrics.noMetricsYet")}
); } const memPct = metrics.memTotalBytes > 0 ? (metrics.memUsedBytes / metrics.memTotalBytes) * 100 : 0; const diskPct = metrics.diskTotalBytes > 0 ? (metrics.diskUsedBytes / metrics.diskTotalBytes) * 100 : 0; const hasGpu = metrics.gpuUtilPct !== null; const gpuMemLabel = metrics.gpuMemBytes !== null ? formatBytes(metrics.gpuMemBytes) : null; // Memory & disk are honest used/total ratios → full-width meters; stack them in // the dock detail, two-up in the dashboard row. const ratioGridClass = density === "full" ? "grid-cols-1" : "grid-cols-1 sm:grid-cols-2"; return (
{/* Resources — used/total ratios with real 0..100% bars. */}
{/* Utilization — CPU% (and GPU% when present) as honest percent gauges. */}
{hasGpu ? ( ) : null}
{/* Activity — load average as labeled mini-stats (no fake bar) + run queue. */}
); }