feat: add Dashboard tab with live GPU overview

Add a new Dashboard tab as the default/first tab (Dashboard - Curve -
Performance - Fans) showing a full GPU overview.

Backend:
- New hal/dashboard.py: one-shot static GPU info via NVML (VBIOS, CUDA
  cores, compute capability, bus width, BAR1/CPU-accessible VRAM,
  Resizable BAR, PCIe max, max clocks, power limits, temp thresholds,
  persistence mode, fan count, serial, board part number, UUID).
  ROP count and VRAM type are best-effort from a per-model table since
  NVML does not expose them.
- New /api/dashboard endpoint.
- MonitoringSample gains live throttle_reasons (+label), PCIe link
  width/generation (downclocks when idle, so read per poll), and VRAM
  temp (from the MEMORY thermal sensor, if exposed).

Frontend:
- New Dashboard component: critical top row (throttling, voltage,
  GPU/VRAM temps), full live-monitor grid with sparklines, and a static
  GPU-information grid. Unavailable fields are omitted.
- useDashboard hook + DashboardInfo type + api.client dashboard().

Also: restrict CORS to localhost origins (end-anchored), make
_int_key_deltas fail closed on bad keys, and clean up lint blockers in
server.py/monitoring.py.
This commit is contained in:
ARIA committed 2026-09-02 16:56:47 +02:00
1 parent 3c55263250
commit da507d55bb
11 files changed
+907 -72

No files matched your search

@@ -0,0 +1,318 @@
import { Loader } from "lucide-react";
import { GaugeCard } from "../Monitor/GaugeCard";
import { fmt } from "../../utils/units";
import type { MonitoringSample, DashboardInfo } from "../../types";
interface Props {
monitor: MonitoringSample | null;
history: MonitoringSample[];
dashboard: DashboardInfo | null;
loading: boolean;
}
function pluck<K extends keyof MonitoringSample>(
history: MonitoringSample[],
key: K,
): number[] {
return history.map((s) => (s[key] as number | null) ?? 0);
}
function pcieLink(
width: number | null | undefined,
gen: number | null | undefined,
): string | null {
if (width == null && gen == null) return null;
return `x${width ?? "?"} Gen${gen ?? "?"}`;
}
/** A static label/value pair. Renders nothing when the value is unavailable. */
function InfoItem({
label,
value,
accent,
}: {
label: string;
value: string | null | undefined;
accent?: string;
}) {
if (value == null || value === "") return null;
return (
<div className="flex flex-col gap-0.5 min-w-0">
<span className="text-xs text-zinc-500">{label}</span>
<span
className={`text-sm font-mono truncate ${accent ?? "text-zinc-200"}`}
title={value}
>
{value}
</span>
</div>
);
}
function TempRow({ label, value }: { label: string; value: number | null }) {
if (value == null) return null;
return (
<div className="flex items-baseline justify-between gap-2">
<span className="text-xs text-zinc-500">{label}</span>
<span className="text-lg font-mono font-semibold text-orange-400">
{fmt.celsius(value)}
</span>
</div>
);
}
export function Dashboard({ monitor, history, dashboard: d, loading }: Props) {
if (loading && !d) {
return (
<div className="bg-zinc-900 rounded-lg p-10 text-center text-zinc-500 flex items-center justify-center gap-2">
<Loader size={18} className="animate-spin" /> Loading dashboard…
</div>
);
}
const throttleLabel = monitor?.throttle_reasons_label ?? null;
const isThrottled = throttleLabel != null && throttleLabel !== "No";
const memUsed = monitor?.mem_used_mib ?? null;
const memTotal = monitor?.mem_total_mib ?? null;
const memLabel =
memUsed != null && memTotal != null
? `${memUsed.toFixed(0)} / ${memTotal.toFixed(0)} MiB`
: "—";
const powerLimit = d?.power_limit_w ?? null;
const powerRange =
d?.power_min_limit_w != null && d?.power_max_limit_w != null
? `${d.power_min_limit_w}–${d.power_max_limit_w} W`
: null;
return (
<div className="flex flex-col gap-4 w-full">
{/* ── Critical row ─────────────────────────────────────────────── */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Throttling */}
<div
className={`rounded-lg p-4 border ${
isThrottled
? "bg-amber-500/10 border-amber-500/30"
: "bg-emerald-500/10 border-emerald-500/30"
}`}
>
<div className="text-xs text-zinc-500 uppercase tracking-wider font-semibold">
Throttling
</div>
<div
className={`text-2xl font-semibold mt-1 break-words ${
isThrottled ? "text-amber-400" : "text-emerald-400"
}`}
>
{throttleLabel ?? "—"}
</div>
</div>
{/* GPU Voltage */}
<div className="bg-zinc-900 rounded-lg p-4 border border-zinc-800">
<div className="text-xs text-zinc-500 uppercase tracking-wider font-semibold">
GPU Voltage
</div>
<div className="text-2xl font-mono font-semibold mt-1 text-violet-400">
{fmt.mv(monitor?.voltage_mv)}
</div>
</div>
{/* Temperature */}
<div className="bg-zinc-900 rounded-lg p-4 border border-zinc-800">
<div className="text-xs text-zinc-500 uppercase tracking-wider font-semibold">
Temperature
</div>
<div className="flex flex-col gap-1 mt-1">
<TempRow label="GPU" value={monitor?.temp_c ?? null} />
<TempRow label="VRAM" value={monitor?.mem_temp_c ?? null} />
</div>
</div>
</div>
{/* ── Live Monitor ─────────────────────────────────────────────── */}
<div className="bg-zinc-900 rounded-lg p-4">
<div className="text-xs text-zinc-500 uppercase tracking-wider font-semibold pb-3 border-b border-zinc-800">
Live Monitor
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 mt-3">
<GaugeCard
label="Core Clock"
value={fmt.mhz(monitor?.clock_mhz)}
history={pluck(history, "clock_mhz")}
color="#34d399"
max={d?.max_graphics_clock_mhz ?? 3000}
/>
<GaugeCard
label="Mem Clock"
value={fmt.mhz(monitor?.mem_clock_mhz)}
history={pluck(history, "mem_clock_mhz")}
color="#67e8f9"
max={d?.max_memory_clock_mhz ?? 20000}
/>
<GaugeCard
label="Voltage"
value={fmt.mv(monitor?.voltage_mv)}
history={pluck(history, "voltage_mv")}
color="#a78bfa"
max={1100}
/>
<GaugeCard
label="Power Draw"
value={fmt.watts(monitor?.power_w)}
history={pluck(history, "power_w")}
color="#f472b6"
max={powerLimit != null && powerLimit > 0 ? powerLimit : 600}
/>
<GaugeCard
label="GPU Util"
value={fmt.pct(monitor?.gpu_util_pct)}
history={pluck(history, "gpu_util_pct")}
color="#facc15"
max={100}
/>
<GaugeCard
label="Mem Util"
value={fmt.pct(monitor?.mem_util_pct)}
history={pluck(history, "mem_util_pct")}
color="#38bdf8"
max={100}
/>
<GaugeCard
label="Fan Speed"
value={fmt.pct(monitor?.fan_pct)}
history={pluck(history, "fan_pct")}
color="#fb923c"
max={100}
/>
<GaugeCard
label="P-State"
value={monitor?.pstate_label ?? "Unknown"}
history={pluck(history, "pstate")}
color="#a8a29e"
max={15}
/>
<GaugeCard
label="VRAM Used"
value={memLabel}
history={pluck(history, "mem_used_mib")}
color="#a78bfa"
max={memTotal ?? 32768}
/>
</div>
</div>
{/* ── GPU Information ──────────────────────────────────────────── */}
<div className="bg-zinc-900 rounded-lg p-4">
<div className="text-xs text-zinc-500 uppercase tracking-wider font-semibold pb-3 border-b border-zinc-800">
GPU Information
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-x-6 gap-y-3 mt-3">
<InfoItem label="Driver" value={d?.driver_version} />
<InfoItem label="VBIOS" value={d?.vbios_version} />
<InfoItem
label="CUDA Cores"
value={d?.cuda_cores != null ? String(d.cuda_cores) : null}
/>
<InfoItem label="CUDA Compute" value={d?.cuda_compute_capability} />
<InfoItem
label="ROP Count"
value={d?.rop_count != null ? String(d.rop_count) : null}
/>
<InfoItem
label="VRAM Type"
value={d?.vram_type}
accent="text-cyan-400"
/>
<InfoItem label="VRAM Total" value={fmt.bytes(d?.vram_total_bytes)} />
<InfoItem
label="Bus Width"
value={
d?.memory_bus_width != null ? `${d.memory_bus_width}-bit` : null
}
/>
<InfoItem
label="CPU Accessible VRAM"
value={fmt.bytes(d?.bar1_total_bytes)}
/>
<InfoItem
label="Resizable BAR"
value={
d?.resizable_bar == null
? null
: d.resizable_bar
? "Enabled"
: "Disabled"
}
accent={
d?.resizable_bar == null
? undefined
: d.resizable_bar
? "text-emerald-400"
: "text-zinc-400"
}
/>
<InfoItem
label="PCIe Link"
value={pcieLink(
monitor?.pcie_link_width,
monitor?.pcie_link_generation,
)}
accent="text-cyan-400"
/>
<InfoItem
label="PCIe Max"
value={pcieLink(
d?.pcie_max_link_width,
d?.pcie_max_link_generation,
)}
/>
<InfoItem
label="Max Core Clock"
value={fmt.mhz(d?.max_graphics_clock_mhz)}
/>
<InfoItem
label="Max Mem Clock"
value={fmt.mhz(d?.max_memory_clock_mhz)}
/>
<InfoItem
label="Power Limit"
value={powerLimit != null ? `${powerLimit} W` : null}
/>
<InfoItem label="Power Range" value={powerRange} />
<InfoItem
label="Temp Slowdown"
value={
d?.temp_slowdown_c != null ? `${d.temp_slowdown_c} °C` : null
}
/>
<InfoItem
label="Temp Shutdown"
value={
d?.temp_shutdown_c != null ? `${d.temp_shutdown_c} °C` : null
}
/>
<InfoItem
label="Persistence Mode"
value={
d?.persistence_mode == null
? null
: d.persistence_mode
? "Enabled"
: "Disabled"
}
/>
<InfoItem
label="Fans"
value={d?.num_fans != null ? String(d.num_fans) : null}
/>
<InfoItem label="Serial" value={d?.serial} />
<InfoItem label="Board Part No." value={d?.board_part_number} />
<InfoItem label="UUID" value={d?.uuid} />
</div>
</div>
</div>
);
}