diff --git a/audit/internal/webui/pages.go b/audit/internal/webui/pages.go
index f5b40a0..f646657 100644
--- a/audit/internal/webui/pages.go
+++ b/audit/internal/webui/pages.go
@@ -392,12 +392,6 @@ func renderMetrics() string {
-
-
Temperature — GPUs
-
-

-
-
Temperature — Ambient Sensors
@@ -421,50 +415,47 @@ func renderMetrics() string {
-
+
+
GPU — Compute Load
+
+

+
+
+
+
GPU — Memory Load
+
+

+
+
+
+
GPU — Power
+
+

+
+
+
+
GPU — Temperature
+
+

+
+
+
`
diff --git a/audit/internal/webui/server.go b/audit/internal/webui/server.go
index c9461c8..7ea24a0 100644
--- a/audit/internal/webui/server.go
+++ b/audit/internal/webui/server.go
@@ -449,7 +449,80 @@ func (h *handler) handleMetricsChartSVG(w http.ResponseWriter, r *http.Request)
yMin = floatPtr(0)
yMax = autoMax120(datasets...)
- // ── GPU sub-charts ────────────────────────────────────────────────────
+ // ── Combined GPU charts (all GPUs on one chart) ───────────────────────
+ case path == "gpu-all-load":
+ title = "GPU Compute Load"
+ h.ringsMu.Lock()
+ for idx, gr := range h.gpuRings {
+ if gr == nil {
+ continue
+ }
+ vUtil, l := gr.Util.snapshot()
+ datasets = append(datasets, vUtil)
+ names = append(names, fmt.Sprintf("GPU %d %%", idx))
+ if len(labels) == 0 {
+ labels = l
+ }
+ }
+ h.ringsMu.Unlock()
+ yMin = floatPtr(0)
+ yMax = floatPtr(100)
+
+ case path == "gpu-all-memload":
+ title = "GPU Memory Load"
+ h.ringsMu.Lock()
+ for idx, gr := range h.gpuRings {
+ if gr == nil {
+ continue
+ }
+ vMem, l := gr.MemUtil.snapshot()
+ datasets = append(datasets, vMem)
+ names = append(names, fmt.Sprintf("GPU %d %%", idx))
+ if len(labels) == 0 {
+ labels = l
+ }
+ }
+ h.ringsMu.Unlock()
+ yMin = floatPtr(0)
+ yMax = floatPtr(100)
+
+ case path == "gpu-all-power":
+ title = "GPU Power"
+ h.ringsMu.Lock()
+ for idx, gr := range h.gpuRings {
+ if gr == nil {
+ continue
+ }
+ vPow, l := gr.Power.snapshot()
+ datasets = append(datasets, vPow)
+ names = append(names, fmt.Sprintf("GPU %d W", idx))
+ if len(labels) == 0 {
+ labels = l
+ }
+ }
+ h.ringsMu.Unlock()
+ yMin = floatPtr(0)
+ yMax = autoMax120(datasets...)
+
+ case path == "gpu-all-temp":
+ title = "GPU Temperature"
+ h.ringsMu.Lock()
+ for idx, gr := range h.gpuRings {
+ if gr == nil {
+ continue
+ }
+ vTemp, l := gr.Temp.snapshot()
+ datasets = append(datasets, vTemp)
+ names = append(names, fmt.Sprintf("GPU %d °C", idx))
+ if len(labels) == 0 {
+ labels = l
+ }
+ }
+ h.ringsMu.Unlock()
+ yMin = floatPtr(0)
+ yMax = autoMax120(datasets...)
+
+ // ── Per-GPU sub-charts ────────────────────────────────────────────────
case strings.HasPrefix(path, "gpu/"):
rest := strings.TrimPrefix(path, "gpu/")
// rest is either "{idx}-load", "{idx}-temp", "{idx}-power", or legacy "{idx}"