Files
nvcurve/frontend/src/components/Dashboard/Dashboard.tsx
T
ARIA da507d55bb 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.
2026-09-02 16:56:47 +02:00

319 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}