From ed3af965859efb13486a7deb4d8d8c583eba3195 Mon Sep 17 00:00:00 2001 From: "CanbiZ (MickLesk)" <47820557+MickLesk@users.noreply.github.com> Date: Tue, 10 Feb 2026 08:16:39 +0100 Subject: [PATCH] Add extended dashboard metrics and helpers Extend FetchDashboardData to collect additional metrics: tool executions, addon installations, GPU usage (including passthrough), error categories, and install durations for averaging. Populate new Dashboard fields (GPUStats, ErrorCategories, TopTools, TopAddons, AvgInstallDuration) and add helper builders (buildGPUStats, buildErrorCategories, buildToolStats, buildAddonStats) that sort results and trim to top-N where appropriate. Keeps existing daily stats and recent records logic unchanged. --- misc/data/dashboard.go | 144 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/misc/data/dashboard.go b/misc/data/dashboard.go index acba2a89d..80f06e465 100644 --- a/misc/data/dashboard.go +++ b/misc/data/dashboard.go @@ -191,6 +191,40 @@ func (p *PBClient) FetchDashboardData(ctx context.Context, days int) (*Dashboard typeCounts[r.Type]++ } + // === Extended metrics tracking === + + // Track tool executions + if r.Type == "tool" && r.ToolName != "" { + toolCounts[r.ToolName]++ + data.TotalTools++ + } + + // Track addon installations + if r.Type == "addon" { + addonCounts[r.NSAPP]++ + data.TotalAddons++ + } + + // Track GPU usage + if r.GPUVendor != "" { + key := r.GPUVendor + if r.GPUPassthrough != "" { + key += "|" + r.GPUPassthrough + } + gpuCounts[key]++ + } + + // Track error categories + if r.Status == "failed" && r.ErrorCategory != "" { + errorCatCounts[r.ErrorCategory]++ + } + + // Track install duration (for averaging) + if r.InstallDuration > 0 { + totalDuration += r.InstallDuration + durationCount++ + } + // Daily stats (use Created field if available) if r.Created != "" { date := r.Created[:10] // "2026-02-09" @@ -224,6 +258,25 @@ func (p *PBClient) FetchDashboardData(ctx context.Context, days int) (*Dashboard // Daily stats for chart data.DailyStats = buildDailyStats(dailySuccess, dailyFailed, days) + // === Extended metrics === + + // GPU stats + data.GPUStats = buildGPUStats(gpuCounts) + + // Error categories + data.ErrorCategories = buildErrorCategories(errorCatCounts) + + // Top tools + data.TopTools = buildToolStats(toolCounts, 10) + + // Top addons + data.TopAddons = buildAddonStats(addonCounts, 10) + + // Average install duration + if durationCount > 0 { + data.AvgInstallDuration = float64(totalDuration) / float64(durationCount) + } + // Recent records (last 20) if len(records) > 20 { data.RecentRecords = records[:20] @@ -502,6 +555,97 @@ func buildDailyStats(success, failed map[string]int, days int) []DailyStat { return result } +// === Extended metrics helper functions === + +func buildGPUStats(gpuCounts map[string]int) []GPUCount { + result := make([]GPUCount, 0, len(gpuCounts)) + for key, count := range gpuCounts { + parts := strings.Split(key, "|") + vendor := parts[0] + passthrough := "" + if len(parts) > 1 { + passthrough = parts[1] + } + result = append(result, GPUCount{ + Vendor: vendor, + Passthrough: passthrough, + Count: count, + }) + } + // Sort by count descending + for i := 0; i < len(result)-1; i++ { + for j := i + 1; j < len(result); j++ { + if result[j].Count > result[i].Count { + result[i], result[j] = result[j], result[i] + } + } + } + return result +} + +func buildErrorCategories(catCounts map[string]int) []ErrorCatCount { + result := make([]ErrorCatCount, 0, len(catCounts)) + for cat, count := range catCounts { + result = append(result, ErrorCatCount{ + Category: cat, + Count: count, + }) + } + // Sort by count descending + for i := 0; i < len(result)-1; i++ { + for j := i + 1; j < len(result); j++ { + if result[j].Count > result[i].Count { + result[i], result[j] = result[j], result[i] + } + } + } + return result +} + +func buildToolStats(toolCounts map[string]int, n int) []ToolCount { + result := make([]ToolCount, 0, len(toolCounts)) + for tool, count := range toolCounts { + result = append(result, ToolCount{ + Tool: tool, + Count: count, + }) + } + // Sort by count descending + for i := 0; i < len(result)-1; i++ { + for j := i + 1; j < len(result); j++ { + if result[j].Count > result[i].Count { + result[i], result[j] = result[j], result[i] + } + } + } + if len(result) > n { + return result[:n] + } + return result +} + +func buildAddonStats(addonCounts map[string]int, n int) []AddonCount { + result := make([]AddonCount, 0, len(addonCounts)) + for addon, count := range addonCounts { + result = append(result, AddonCount{ + Addon: addon, + Count: count, + }) + } + // Sort by count descending + for i := 0; i < len(result)-1; i++ { + for j := i + 1; j < len(result); j++ { + if result[j].Count > result[i].Count { + result[i], result[j] = result[j], result[i] + } + } + } + if len(result) > n { + return result[:n] + } + return result +} + // DashboardHTML returns the embedded dashboard HTML func DashboardHTML() string { return `