package webui import ( "encoding/json" "fmt" "html" "path/filepath" "regexp" "sort" "strconv" "strings" "bee/audit/internal/app" "bee/audit/internal/schema" ) // renderPage dispatches to the appropriate page renderer. func renderPage(page string, opts HandlerOptions) string { var pageID, title, body string switch page { case "dashboard", "": pageID = "dashboard" title = "Dashboard" body = renderDashboard(opts) case "audit": pageID = "audit" title = "Audit" body = renderAudit() case "validate": pageID = "validate" title = "Validate" body = renderValidate(opts) case "burn": pageID = "burn" title = "Burn" body = renderBurn() case "benchmark": pageID = "benchmark" title = "Benchmark" body = renderBenchmark(opts) case "tasks": pageID = "tasks" title = "Tasks" body = renderTasks() case "tools": pageID = "tools" title = "Tools" body = renderTools() // Legacy routes kept accessible but not in nav case "metrics": pageID = "metrics" title = "Live Metrics" body = renderMetrics() case "tests": pageID = "validate" title = "Acceptance Tests" body = renderValidate(opts) case "burn-in": pageID = "burn" title = "Burn-in Tests" body = renderBurn() case "network": pageID = "network" title = "Network" body = renderNetwork() case "services": pageID = "services" title = "Services" body = renderServices() case "export": pageID = "export" title = "Export" body = renderExport(opts.ExportDir) case "install": pageID = "install" title = "Install to Disk" body = renderInstall() default: pageID = "dashboard" title = "Not Found" body = `
Page not found.
` } return layoutHead(opts.Title+" — "+title) + layoutNav(pageID, opts.BuildLabel) + `

` + html.EscapeString(title) + `

` + body + `
` + renderAuditModal() + `
` + `` + `` } // ── Dashboard ───────────────────────────────────────────────────────────────── func renderDashboard(opts HandlerOptions) string { var b strings.Builder b.WriteString(renderAuditStatusBanner(opts)) b.WriteString(renderHardwareSummaryCard(opts)) b.WriteString(renderHealthCard(opts)) b.WriteString(renderMetrics()) b.WriteString(``) return b.String() } // renderAuditStatusBanner shows a live progress banner when an audit task is // running and auto-reloads the page when it completes. func renderAuditStatusBanner(opts HandlerOptions) string { // If audit data already exists, no banner needed — data is fresh. // We still inject the polling script so a newly-triggered audit also reloads. hasData := false if _, err := loadSnapshot(opts.AuditPath); err == nil { hasData = true } _ = hasData return ` ` } func renderAudit() string { return `
Audit Viewer
` } func renderHardwareSummaryCard(opts HandlerOptions) string { const cardID = ` id="hw-summary-card"` data, err := loadSnapshot(opts.AuditPath) if err != nil { return `
Hardware Summary
` } var ingest schema.HardwareIngestRequest if err := json.Unmarshal(data, &ingest); err != nil { return `
Hardware Summary
Parse error
` } hw := ingest.Hardware var records []app.ComponentStatusRecord if db, err := app.OpenComponentStatusDB(filepath.Join(opts.ExportDir, "component-status.json")); err == nil { records = db.All() } var b strings.Builder b.WriteString(`
Hardware Summary
`) // Server identity block above the component table. { var model, serial string parts := []string{} if hw.Board.Manufacturer != nil && strings.TrimSpace(*hw.Board.Manufacturer) != "" { parts = append(parts, strings.TrimSpace(*hw.Board.Manufacturer)) } if hw.Board.ProductName != nil && strings.TrimSpace(*hw.Board.ProductName) != "" { parts = append(parts, strings.TrimSpace(*hw.Board.ProductName)) } if len(parts) > 0 { model = strings.Join(parts, " ") } serial = strings.TrimSpace(hw.Board.SerialNumber) if model != "" || serial != "" { b.WriteString(`
`) if model != "" { fmt.Fprintf(&b, `
%s
`, html.EscapeString(model)) } if serial != "" { fmt.Fprintf(&b, `
S/N: %s
`, html.EscapeString(serial)) } b.WriteString(`
`) } } b.WriteString(``) // writeRow renders one component row. compType is the URL path segment for the detail // endpoint (e.g. "cpu"). Pass "" for rows that have no detail view. writeRow := func(label, value, badgeHTML, compType string) { var labelHTML string if compType != "" { labelHTML = fmt.Sprintf( `%s`, compType, html.EscapeString(label)) } else { labelHTML = html.EscapeString(label) } fmt.Fprintf(&b, ``, labelHTML, html.EscapeString(value), badgeHTML) } writeRow("CPU", hwDescribeCPU(hw), renderComponentChips(matchedRecords(records, []string{"cpu:all"}, nil)), "cpu") writeRow("Memory", hwDescribeMemory(hw), renderComponentChips(matchedRecords(records, []string{"memory:all"}, []string{"memory:"})), "memory") writeRow("Storage", hwDescribeStorage(hw), renderComponentChips(matchedRecords(records, []string{"storage:all"}, []string{"storage:"})), "storage") writeRow("GPU", hwDescribeGPU(hw), renderComponentChips(matchedRecords(records, nil, []string{"pcie:gpu:"})), "gpu") psuMatched := matchedRecords(records, nil, []string{"psu:"}) if len(psuMatched) == 0 && len(hw.PowerSupplies) > 0 { // No PSU records yet — synthesise a single chip from IPMI status. psuStatus := hwPSUStatus(hw.PowerSupplies) psuMatched = []app.ComponentStatusRecord{{ComponentKey: "psu:ipmi", Status: psuStatus}} } writeRow("PSU", hwDescribePSU(hw), renderComponentChips(psuMatched), "psu") if nicDesc := hwDescribeNIC(hw); nicDesc != "" { writeRow("Network", nicDesc, "", "") } b.WriteString(`
%s%s%s
`) b.WriteString(`
`) return b.String() } // hwDescribeCPU returns a human-readable CPU summary, e.g. "2× Intel Xeon Gold 6338". func hwDescribeCPU(hw schema.HardwareSnapshot) string { counts := map[string]int{} order := []string{} for _, cpu := range hw.CPUs { model := "Unknown CPU" if cpu.Model != nil && *cpu.Model != "" { model = *cpu.Model } if counts[model] == 0 { order = append(order, model) } counts[model]++ } if len(order) == 0 { return "—" } parts := make([]string, 0, len(order)) for _, m := range order { if counts[m] > 1 { parts = append(parts, fmt.Sprintf("%d× %s", counts[m], m)) } else { parts = append(parts, m) } } return strings.Join(parts, ", ") } // hwDescribeMemory returns a summary like "16× 32 GB DDR4". func hwDescribeMemory(hw schema.HardwareSnapshot) string { type key struct { sizeMB int typ string } counts := map[key]int{} order := []key{} for _, dimm := range hw.Memory { if dimm.SizeMB == nil || *dimm.SizeMB == 0 { continue } t := "" if dimm.Type != nil { t = *dimm.Type } k := key{*dimm.SizeMB, t} if counts[k] == 0 { order = append(order, k) } counts[k]++ } if len(order) == 0 { return "—" } parts := make([]string, 0, len(order)) for _, k := range order { gb := k.sizeMB / 1024 desc := fmt.Sprintf("%d× %d GB", counts[k], gb) if k.typ != "" { desc += " " + k.typ } parts = append(parts, desc) } return strings.Join(parts, ", ") } // hwDescribeStorage returns a summary like "4× 3.84 TB NVMe, 2× 1.92 TB SATA". func hwDescribeStorage(hw schema.HardwareSnapshot) string { type key struct { sizeGB int iface string } counts := map[key]int{} order := []key{} for _, disk := range hw.Storage { sz := 0 if disk.SizeGB != nil { sz = *disk.SizeGB } iface := "" if disk.Interface != nil { iface = *disk.Interface } else if disk.Type != nil { iface = *disk.Type } k := key{sz, iface} if counts[k] == 0 { order = append(order, k) } counts[k]++ } if len(order) == 0 { return "—" } parts := make([]string, 0, len(order)) for _, k := range order { var sizeStr string if k.sizeGB >= 1000 { sizeStr = fmt.Sprintf("%.2g TB", float64(k.sizeGB)/1000) } else if k.sizeGB > 0 { sizeStr = fmt.Sprintf("%d GB", k.sizeGB) } else { sizeStr = "?" } desc := fmt.Sprintf("%d× %s", counts[k], sizeStr) if k.iface != "" { desc += " " + k.iface } parts = append(parts, desc) } return strings.Join(parts, ", ") } // hwDescribeGPU returns a summary like "8× NVIDIA H100 80GB". func hwDescribeGPU(hw schema.HardwareSnapshot) string { counts := map[string]int{} order := []string{} for _, dev := range hw.PCIeDevices { if dev.DeviceClass == nil { continue } if !isGPUDeviceClass(*dev.DeviceClass) { continue } model := "Unknown GPU" if dev.Model != nil && *dev.Model != "" { model = *dev.Model } if counts[model] == 0 { order = append(order, model) } counts[model]++ } if len(order) == 0 { return "—" } parts := make([]string, 0, len(order)) for _, m := range order { if counts[m] > 1 { parts = append(parts, fmt.Sprintf("%d× %s", counts[m], m)) } else { parts = append(parts, m) } } return strings.Join(parts, ", ") } // hwPSUStatus returns "OK", "CRITICAL", "WARNING", or "UNKNOWN" based on // PSU statuses from the audit snapshot. Used as fallback when component-status.json // has no psu: records yet (e.g. first boot before audit writes them). func hwPSUStatus(psus []schema.HardwarePowerSupply) string { worst := "UNKNOWN" for _, psu := range psus { if psu.Status == nil { continue } switch strings.ToUpper(strings.TrimSpace(*psu.Status)) { case "CRITICAL": return "CRITICAL" case "WARNING": if worst != "CRITICAL" { worst = "WARNING" } case "OK": if worst == "UNKNOWN" { worst = "OK" } } } return worst } // hwDescribePSU returns a summary like "2× 1600 W" or "2× PSU". func hwDescribePSU(hw schema.HardwareSnapshot) string { n := len(hw.PowerSupplies) if n == 0 { return "—" } // Try to get a consistent wattage watt := 0 consistent := true for _, psu := range hw.PowerSupplies { if psu.WattageW == nil { consistent = false break } if watt == 0 { watt = *psu.WattageW } else if *psu.WattageW != watt { consistent = false break } } if consistent && watt > 0 { return fmt.Sprintf("%d× %d W", n, watt) } return fmt.Sprintf("%d× PSU", n) } // hwDescribeNIC returns a summary like "2× Mellanox ConnectX-6". func hwDescribeNIC(hw schema.HardwareSnapshot) string { counts := map[string]int{} order := []string{} for _, dev := range hw.PCIeDevices { isNIC := false if dev.DeviceClass != nil { c := strings.ToLower(strings.TrimSpace(*dev.DeviceClass)) isNIC = c == "ethernetcontroller" || c == "networkcontroller" || strings.Contains(c, "fibrechannel") } if !isNIC && len(dev.MacAddresses) == 0 { continue } model := "" if dev.Model != nil && *dev.Model != "" { model = *dev.Model } else if dev.Manufacturer != nil && *dev.Manufacturer != "" { model = *dev.Manufacturer + " NIC" } else { model = "NIC" } if counts[model] == 0 { order = append(order, model) } counts[model]++ } if len(order) == 0 { return "" } parts := make([]string, 0, len(order)) for _, m := range order { if counts[m] > 1 { parts = append(parts, fmt.Sprintf("%d× %s", counts[m], m)) } else { parts = append(parts, m) } } return strings.Join(parts, ", ") } func isGPUDeviceClass(class string) bool { switch strings.TrimSpace(class) { case "VideoController", "DisplayController", "ProcessingAccelerator": return true default: return false } } func renderAuditModal() string { return ` ` } func renderHealthCard(opts HandlerOptions) string { data, err := loadSnapshot(filepath.Join(opts.ExportDir, "runtime-health.json")) if err != nil { return `
Runtime Health
No data
` } var health schema.RuntimeHealth if err := json.Unmarshal(data, &health); err != nil { return `
Runtime Health
Parse error
` } status := strings.TrimSpace(health.Status) if status == "" { status = "UNKNOWN" } badge := "badge-ok" if status == "PARTIAL" { badge = "badge-warn" } else if status == "FAIL" || status == "FAILED" { badge = "badge-err" } var b strings.Builder b.WriteString(`
Runtime Health
`) b.WriteString(fmt.Sprintf(`
%s
`, badge, html.EscapeString(status))) if checkedAt := strings.TrimSpace(health.CheckedAt); checkedAt != "" { b.WriteString(`
Checked at: ` + html.EscapeString(checkedAt) + `
`) } rows := []runtimeHealthRow{ buildRuntimeExportRow(health), buildRuntimeNetworkRow(health), buildRuntimeDriverRow(health), buildRuntimeAccelerationRow(health), buildRuntimeToolsRow(health), buildRuntimeServicesRow(health), buildRuntimeUSBExportRow(health), buildRuntimeToRAMRow(health), } b.WriteString(``) for _, row := range rows { b.WriteString(``) } b.WriteString(`
CheckStatusSourceIssue
` + html.EscapeString(row.Title) + `` + runtimeStatusBadge(row.Status) + `` + html.EscapeString(row.Source) + `` + rowIssueHTML(row.Issue) + `
`) b.WriteString(`
`) return b.String() } type runtimeHealthRow struct { Title string Status string Source string Issue string } func buildRuntimeExportRow(health schema.RuntimeHealth) runtimeHealthRow { issue := runtimeIssueDescriptions(health.Issues, "export_dir_unavailable") status := "UNKNOWN" switch { case issue != "": status = "FAILED" case strings.TrimSpace(health.ExportDir) != "": status = "OK" } source := "os.MkdirAll" if dir := strings.TrimSpace(health.ExportDir); dir != "" { source += " " + dir } return runtimeHealthRow{Title: "Export Directory", Status: status, Source: source, Issue: issue} } func buildRuntimeNetworkRow(health schema.RuntimeHealth) runtimeHealthRow { status := strings.TrimSpace(health.NetworkStatus) if status == "" { status = "UNKNOWN" } issue := runtimeIssueDescriptions(health.Issues, "dhcp_failed") return runtimeHealthRow{Title: "Network", Status: status, Source: "ListInterfaces / DHCP", Issue: issue} } func buildRuntimeDriverRow(health schema.RuntimeHealth) runtimeHealthRow { issue := runtimeIssueDescriptions(health.Issues, "nvidia_kernel_module_missing", "nvidia_modeset_failed", "amdgpu_kernel_module_missing") status := "UNKNOWN" switch { case health.DriverReady && issue == "": status = "OK" case health.DriverReady: status = "PARTIAL" case issue != "": status = "FAILED" } return runtimeHealthRow{Title: "NVIDIA/AMD Driver", Status: status, Source: "lsmod / vendor probe", Issue: issue} } func buildRuntimeAccelerationRow(health schema.RuntimeHealth) runtimeHealthRow { issue := runtimeIssueDescriptions(health.Issues, "cuda_runtime_not_ready", "rocm_smi_unavailable") status := "UNKNOWN" switch { case health.CUDAReady && issue == "": status = "OK" case health.CUDAReady: status = "PARTIAL" case issue != "": status = "FAILED" } return runtimeHealthRow{Title: "CUDA / ROCm", Status: status, Source: "bee-gpu-burn / rocm-smi", Issue: issue} } func buildRuntimeToolsRow(health schema.RuntimeHealth) runtimeHealthRow { if len(health.Tools) == 0 { return runtimeHealthRow{Title: "Required Utilities", Status: "UNKNOWN", Source: "CheckTools", Issue: "No tool status data."} } missing := make([]string, 0) for _, tool := range health.Tools { if !tool.OK { missing = append(missing, tool.Name) } } status := "OK" issue := "" if len(missing) > 0 { status = "PARTIAL" issue = "Missing: " + strings.Join(missing, ", ") } return runtimeHealthRow{Title: "Required Utilities", Status: status, Source: "CheckTools", Issue: issue} } func buildRuntimeServicesRow(health schema.RuntimeHealth) runtimeHealthRow { if len(health.Services) == 0 { return runtimeHealthRow{Title: "Bee Services", Status: "UNKNOWN", Source: "systemctl is-active", Issue: "No service status data."} } nonActive := make([]string, 0) for _, svc := range health.Services { state := strings.TrimSpace(strings.ToLower(svc.Status)) // "inactive" is OK for oneshot services that have completed successfully // (bee-sshsetup, bee-preflight, bee-audit, bee-network, etc.). // Only "failed" is a genuine problem. switch state { case "active", "activating", "deactivating", "reloading", "inactive": // OK — service is running, transitioning normally, or completed successfully default: nonActive = append(nonActive, svc.Name+"="+svc.Status) } } status := "OK" issue := "" if len(nonActive) > 0 { status = "PARTIAL" issue = strings.Join(nonActive, ", ") } return runtimeHealthRow{Title: "Bee Services", Status: status, Source: "ServiceState", Issue: issue} } func buildRuntimeUSBExportRow(health schema.RuntimeHealth) runtimeHealthRow { path := strings.TrimSpace(health.USBExportPath) if path != "" { return runtimeHealthRow{ Title: "USB Export Drive", Status: "OK", Source: "/proc/mounts + lsblk", Issue: path, } } return runtimeHealthRow{ Title: "USB Export Drive", Status: "WARNING", Source: "/proc/mounts + lsblk", Issue: "No writable USB drive mounted. Plug in a USB drive to enable log export.", } } func buildRuntimeToRAMRow(health schema.RuntimeHealth) runtimeHealthRow { switch strings.ToLower(strings.TrimSpace(health.ToRAMStatus)) { case "ok": return runtimeHealthRow{ Title: "LiveCD in RAM", Status: "OK", Source: "live-boot / /proc/mounts", Issue: "", } case "partial": return runtimeHealthRow{ Title: "LiveCD in RAM", Status: "WARNING", Source: "live-boot / /proc/mounts / /dev/shm/bee-live", Issue: "Partial or staged RAM copy detected. System is not fully running from RAM; Copy to RAM can be retried.", } case "failed": return runtimeHealthRow{ Title: "LiveCD in RAM", Status: "FAILED", Source: "live-boot / /proc/mounts", Issue: "toram boot parameter set but ISO is not mounted from RAM. Copy may have failed.", } default: // toram not active — ISO still on original boot media (USB/CD) return runtimeHealthRow{ Title: "LiveCD in RAM", Status: "WARNING", Source: "live-boot / /proc/mounts", Issue: "ISO not copied to RAM. Use \u201cCopy to RAM\u201d to free the boot drive and improve performance.", } } } func buildHardwareComponentRows(exportDir string) []runtimeHealthRow { path := filepath.Join(exportDir, "component-status.json") db, err := app.OpenComponentStatusDB(path) if err != nil { return []runtimeHealthRow{ {Title: "CPU Component Health", Status: "UNKNOWN", Source: "component-status.json", Issue: "Component status DB not available."}, {Title: "Memory Component Health", Status: "UNKNOWN", Source: "component-status.json", Issue: "Component status DB not available."}, {Title: "Storage Component Health", Status: "UNKNOWN", Source: "component-status.json", Issue: "Component status DB not available."}, {Title: "GPU Component Health", Status: "UNKNOWN", Source: "component-status.json", Issue: "Component status DB not available."}, {Title: "PSU Component Health", Status: "UNKNOWN", Source: "component-status.json", Issue: "No PSU component checks recorded."}, } } records := db.All() return []runtimeHealthRow{ aggregateComponentStatus("CPU", records, []string{"cpu:all"}, nil), aggregateComponentStatus("Memory", records, []string{"memory:all"}, []string{"memory:"}), aggregateComponentStatus("Storage", records, []string{"storage:all"}, []string{"storage:"}), aggregateComponentStatus("GPU", records, nil, []string{"pcie:gpu:"}), aggregateComponentStatus("PSU", records, nil, []string{"psu:"}), } } // matchedRecords returns all ComponentStatusRecord entries whose key matches // any exact key or any of the given prefixes. Used for per-device chip rendering. func firstNonEmpty(vals ...string) string { for _, v := range vals { if v != "" { return v } } return "" } func matchedRecords(records []app.ComponentStatusRecord, exact []string, prefixes []string) []app.ComponentStatusRecord { var matched []app.ComponentStatusRecord for _, rec := range records { key := strings.TrimSpace(rec.ComponentKey) if key == "" { continue } if containsExactKey(key, exact) || hasAnyPrefix(key, prefixes) { matched = append(matched, rec) } } return matched } func aggregateComponentStatus(title string, records []app.ComponentStatusRecord, exact []string, prefixes []string) runtimeHealthRow { matched := make([]app.ComponentStatusRecord, 0) for _, rec := range records { key := strings.TrimSpace(rec.ComponentKey) if key == "" { continue } if containsExactKey(key, exact) || hasAnyPrefix(key, prefixes) { matched = append(matched, rec) } } if len(matched) == 0 { return runtimeHealthRow{Title: title, Status: "UNKNOWN", Source: "component-status.json", Issue: "No component status data."} } maxSev := -1 for _, rec := range matched { if sev := runtimeComponentSeverity(rec.Status); sev > maxSev { maxSev = sev } } status := "UNKNOWN" switch maxSev { case 3: status = "CRITICAL" case 2: status = "WARNING" case 1: status = "OK" } sources := make([]string, 0) sourceSeen := map[string]struct{}{} issues := make([]string, 0) issueSeen := map[string]struct{}{} for _, rec := range matched { if runtimeComponentSeverity(rec.Status) != maxSev { continue } source := latestComponentSource(rec) if source == "" { source = "component-status.json" } if _, ok := sourceSeen[source]; !ok { sourceSeen[source] = struct{}{} sources = append(sources, source) } issue := strings.TrimSpace(rec.ErrorSummary) if issue == "" { issue = latestComponentDetail(rec) } if issue == "" { continue } if _, ok := issueSeen[issue]; ok { continue } issueSeen[issue] = struct{}{} issues = append(issues, issue) } if len(sources) == 0 { sources = append(sources, "component-status.json") } issue := strings.Join(issues, "; ") if issue == "" { issue = "—" } return runtimeHealthRow{ Title: title, Status: status, Source: strings.Join(sources, ", "), Issue: issue, } } func containsExactKey(key string, exact []string) bool { for _, candidate := range exact { if key == candidate { return true } } return false } func hasAnyPrefix(key string, prefixes []string) bool { for _, prefix := range prefixes { if strings.HasPrefix(key, prefix) { return true } } return false } func runtimeComponentSeverity(status string) int { switch strings.TrimSpace(strings.ToLower(status)) { case "critical": return 3 case "warning": return 2 case "ok": return 1 default: return 0 } } func latestComponentSource(rec app.ComponentStatusRecord) string { if len(rec.History) == 0 { return "" } return strings.TrimSpace(rec.History[len(rec.History)-1].Source) } func latestComponentDetail(rec app.ComponentStatusRecord) string { if len(rec.History) == 0 { return "" } return strings.TrimSpace(rec.History[len(rec.History)-1].Detail) } func runtimeIssueDescriptions(issues []schema.RuntimeIssue, codes ...string) string { if len(issues) == 0 || len(codes) == 0 { return "" } allowed := make(map[string]struct{}, len(codes)) for _, code := range codes { allowed[code] = struct{}{} } messages := make([]string, 0) for _, issue := range issues { if _, ok := allowed[issue.Code]; !ok { continue } desc := strings.TrimSpace(issue.Description) if desc == "" { desc = issue.Code } messages = append(messages, desc) } return strings.Join(messages, "; ") } // chipLetterClass maps a component status to a single display letter and CSS class. func chipLetterClass(status string) (letter, cls string) { switch strings.ToUpper(strings.TrimSpace(status)) { case "OK": return "O", "chip-ok" case "WARNING", "WARN", "PARTIAL": return "W", "chip-warn" case "CRITICAL", "FAIL", "FAILED", "ERROR": return "F", "chip-fail" default: return "?", "chip-unknown" } } // renderComponentChips renders one 20×20 chip per ComponentStatusRecord. // Hover tooltip shows component key, status, error summary and last check time. // Falls back to a single unknown chip when no records are available. func renderComponentChips(matched []app.ComponentStatusRecord) string { if len(matched) == 0 { return `?` } sort.Slice(matched, func(i, j int) bool { return matched[i].ComponentKey < matched[j].ComponentKey }) var b strings.Builder b.WriteString(``) for _, rec := range matched { letter, cls := chipLetterClass(rec.Status) var tooltip strings.Builder tooltip.WriteString(rec.ComponentKey) tooltip.WriteString(": ") tooltip.WriteString(firstNonEmpty(rec.Status, "UNKNOWN")) if rec.ErrorSummary != "" { tooltip.WriteString(" — ") tooltip.WriteString(rec.ErrorSummary) } if !rec.LastCheckedAt.IsZero() { fmt.Fprintf(&tooltip, " (checked %s)", rec.LastCheckedAt.Format("15:04:05")) } fmt.Fprintf(&b, `%s`, cls, html.EscapeString(tooltip.String()), letter) } b.WriteString(``) return b.String() } func runtimeStatusBadge(status string) string { status = strings.ToUpper(strings.TrimSpace(status)) badge := "badge-unknown" switch status { case "OK": badge = "badge-ok" case "PARTIAL", "WARNING", "WARN": badge = "badge-warn" case "FAIL", "FAILED", "CRITICAL": badge = "badge-err" } return `` + html.EscapeString(status) + `` } func rowIssueHTML(issue string) string { issue = strings.TrimSpace(issue) if issue == "" { return `` } return html.EscapeString(issue) } var aerStatusRe = regexp.MustCompile(`aer_status:\s*0x([0-9a-fA-F]{1,8})`) // decodeAERStatus parses an AER status hex value from a kernel error detail string // and returns a human-readable list of set bit names with correctable/uncorrectable label, // or "" if no AER status is found. func decodeAERStatus(detail string) string { m := aerStatusRe.FindStringSubmatch(detail) if m == nil { return "" } v64, err := strconv.ParseUint(m[1], 16, 32) if err != nil { return "" } val := uint32(v64) type bitDef struct { bit uint32 name string } corrBits := []bitDef{ {0, "Receiver Error"}, {6, "Replay Timer Timeout"}, {7, "Advisory Non-Fatal"}, {8, "Corrected Internal Error"}, {9, "Header Log Overflow"}, {13, "Replay Num Rollover"}, {14, "Bad DLLP"}, {15, "Bad TLP"}, } uncorrBits := []bitDef{ {4, "Data Link Protocol Error"}, {5, "Surprise Down Error"}, {12, "Poisoned TLP Received"}, {13, "Flow Control Protocol Error"}, {14, "Completion Timeout"}, {15, "Completer Abort"}, {16, "Unexpected Completion"}, {17, "Receiver Overflow"}, {18, "Malformed TLP"}, {19, "ECRC Error"}, {20, "Unsupported Request Error"}, {21, "ACS Violation"}, {22, "Uncorrectable Internal Error"}, } var corrNames, uncorrNames []string for _, b := range corrBits { if val&(1<= len(uncorrNames) && len(corrNames) > 0 { return strings.Join(corrNames, ", ") + " (correctable)" } if len(uncorrNames) > 0 { return strings.Join(uncorrNames, ", ") + " (uncorrectable)" } return fmt.Sprintf("unknown bits: 0x%08x", val) } // renderSparkline returns a small inline SVG showing non-OK events over time. // Events are positioned proportionally along the time axis; if all share the same // timestamp they are spaced evenly. Width is always 100px. func renderSparkline(history []app.ComponentStatusEntry) string { const ( svgW = 100 svgH = 20 barW = 3 barH = 14 ) var events []app.ComponentStatusEntry for _, e := range history { if e.Status != "OK" { events = append(events, e) } } if len(events) == 0 { return "" } n := len(events) barColor := func(status string) string { if status == "Critical" { return "#c0392b" } return "#d97706" } yTop := (svgH - barH) / 2 var bars strings.Builder if n == 1 { x := (svgW - barW) / 2 fmt.Fprintf(&bars, ``, x, yTop, barW, barH, barColor(events[0].Status)) } else { minT := events[0].At maxT := events[n-1].At dur := maxT.Sub(minT).Seconds() for i, e := range events { var x int if dur <= 0 { step := svgW / n x = i*step + (step-barW)/2 } else { frac := e.At.Sub(minT).Seconds() / dur x = int(frac * float64(svgW-barW)) } fmt.Fprintf(&bars, ``, x, yTop, barW, barH, barColor(e.Status)) } } return fmt.Sprintf( ``+ `%s`, svgW, svgH, svgW, svgH, bars.String()) } // renderComponentDetail renders a modal content fragment for one component type. // Called by handleAPIComponentDetail and displayed inside #component-detail-dialog. func renderComponentDetail(title string, records []app.ComponentStatusRecord) string { var b strings.Builder fmt.Fprintf(&b, `
`) fmt.Fprintf(&b, `
`) fmt.Fprintf(&b, `%s — Status Detail`, html.EscapeString(title)) b.WriteString(``) b.WriteString(`
`) if len(records) == 0 { b.WriteString(`

No status data recorded yet for this component type.

`) b.WriteString(`
`) return b.String() } sort.Slice(records, func(i, j int) bool { return records[i].ComponentKey < records[j].ComponentKey }) for _, rec := range records { letter, cls := chipLetterClass(rec.Status) // Count non-OK events across the full history for the badge + sparkline. warnCount := 0 for _, e := range rec.History { if e.Status != "OK" { warnCount++ } } fmt.Fprintf(&b, `
`) fmt.Fprintf(&b, `
`) fmt.Fprintf(&b, `%s`, cls, letter) fmt.Fprintf(&b, `%s`, html.EscapeString(rec.ComponentKey)) if !rec.LastCheckedAt.IsZero() { fmt.Fprintf(&b, `checked %s`, rec.LastCheckedAt.Format("2006-01-02 15:04:05")) } if warnCount > 0 { noun := "events" if warnCount == 1 { noun = "event" } fmt.Fprintf(&b, `%d %s`, warnCount, noun) b.WriteString(renderSparkline(rec.History)) } b.WriteString(`
`) if rec.ErrorSummary != "" { fmt.Fprintf(&b, `
%s
`, html.EscapeString(rec.ErrorSummary)) if decoded := decodeAERStatus(rec.ErrorSummary); decoded != "" { fmt.Fprintf(&b, `
AER: %s
`, html.EscapeString(decoded)) } } // History table — newest first, cap at 20 entries. history := rec.History if len(history) > 20 { history = history[len(history)-20:] } b.WriteString(``) b.WriteString(``) for i := len(history) - 1; i >= 0; i-- { e := history[i] eLetter, eCls := chipLetterClass(e.Status) detail := e.Detail if detail == "" { detail = "—" } fmt.Fprintf(&b, ``, html.EscapeString(e.At.Format("2006-01-02 15:04:05")), eCls, eLetter, html.EscapeString(e.Source), html.EscapeString(detail), ) } b.WriteString(`
TimeStatusSourceDetail
%s%s%s%s
`) b.WriteString(`
`) } b.WriteString(``) return b.String() }