package webui import ( "encoding/json" "fmt" "html" "net/url" "os" "path/filepath" "sort" "strings" ) // ── Layout ──────────────────────────────────────────────────────────────────── func layoutHead(title string) string { return ` ` + html.EscapeString(title) + ` ` } func layoutNav(active string, buildLabel string) string { items := []struct{ id, label, href, onclick string }{ {"dashboard", "Dashboard", "/", ""}, {"audit", "Audit", "/audit", ""}, {"validate", "Validate", "/validate", ""}, {"burn", "Burn", "/burn", ""}, {"tasks", "Tasks", "/tasks", ""}, {"tools", "Tools", "/tools", ""}, } var b strings.Builder b.WriteString(``) return b.String() } // 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() case "burn": pageID = "burn" title = "Burn" body = renderBurn() 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() 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(renderHardwareSummaryCard(opts)) b.WriteString(renderHealthCard(opts)) b.WriteString(renderMetrics()) return b.String() } func renderAudit() string { return `
Audit Viewer
` } func renderHardwareSummaryCard(opts HandlerOptions) string { data, err := loadSnapshot(opts.AuditPath) if err != nil { return `
Hardware Summary
No audit data
` } // Parse just enough fields for the summary banner var snap struct { Summary struct { CPU struct{ Model string } Memory struct{ TotalGB float64 } Storage []struct{ Device, Model, Size string } GPUs []struct{ Model string } PSUs []struct{ Model string } } Network struct { Interfaces []struct { Name string IPv4 []string State string } } } // Try to extract top-level fields loosely var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { return `
Hardware Summary
Parse error
` } _ = snap // Also load runtime-health for badges type componentHealth struct { FailCount int `json:"fail_count"` WarnCount int `json:"warn_count"` } type healthSummary struct { CPU componentHealth `json:"cpu"` Memory componentHealth `json:"memory"` Storage componentHealth `json:"storage"` GPU componentHealth `json:"gpu"` PSU componentHealth `json:"psu"` Network componentHealth `json:"network"` } var health struct { HardwareHealth healthSummary `json:"hardware_health"` } if hdata, herr := loadSnapshot(filepath.Join(opts.ExportDir, "runtime-health.json")); herr == nil { _ = json.Unmarshal(hdata, &health) } badge := func(h componentHealth) string { if h.FailCount > 0 { return `FAIL` } if h.WarnCount > 0 { return `WARN` } return `OK` } // Extract readable strings from raw JSON getString := func(key string) string { v, ok := raw[key] if !ok { return "" } var s string if err := json.Unmarshal(v, &s); err == nil { return s } return "" } cpuModel := getString("cpu_model") memStr := getString("memory_summary") gpuSummary := getString("gpu_summary") var b strings.Builder b.WriteString(`
Hardware Summary
`) b.WriteString(``) writeRow := func(label, value, badgeHTML string) { b.WriteString(fmt.Sprintf(``, html.EscapeString(label), html.EscapeString(value), badgeHTML)) } if cpuModel != "" { writeRow("CPU", cpuModel, badge(health.HardwareHealth.CPU)) } else { writeRow("CPU", "—", badge(health.HardwareHealth.CPU)) } if memStr != "" { writeRow("Memory", memStr, badge(health.HardwareHealth.Memory)) } else { writeRow("Memory", "—", badge(health.HardwareHealth.Memory)) } if gpuSummary != "" { writeRow("GPU", gpuSummary, badge(health.HardwareHealth.GPU)) } else { writeRow("GPU", "—", badge(health.HardwareHealth.GPU)) } writeRow("Storage", "—", badge(health.HardwareHealth.Storage)) writeRow("PSU", "—", badge(health.HardwareHealth.PSU)) b.WriteString(`
%s%s%s
`) b.WriteString(`
`) return b.String() } 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 map[string]any if err := json.Unmarshal(data, &health); err != nil { return `
Runtime Health
Parse error
` } status := fmt.Sprintf("%v", health["status"]) 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 issues, ok := health["issues"].([]any); ok && len(issues) > 0 { b.WriteString(`
Issues:
`) for _, issue := range issues { if m, ok := issue.(map[string]any); ok { b.WriteString(html.EscapeString(fmt.Sprintf("%v: %v", m["code"], m["message"])) + "
") } } b.WriteString(`
`) } b.WriteString(`
`) return b.String() } // ── Metrics ─────────────────────────────────────────────────────────────────── func renderMetrics() string { return `

Live metrics — updated every 2 seconds.

Server — Load
CPU/Mem load
Temperature — CPU
CPU temperature
Temperature — Ambient Sensors
Ambient temperature sensors
Server — Power
System power
GPU — Compute Load
GPU compute load
GPU — Memory Load
GPU memory load
GPU — Power
GPU power
GPU — Temperature
GPU temperature
` } // ── Validate (Acceptance Tests) ─────────────────────────────────────────────── func renderValidate() string { return `
Non-destructive: Validate tests collect diagnostics only. They do not write to disks, do not run sustained load, and do not increment hardware wear counters.

Tasks continue in the background — view progress in Tasks.

Run All Tests
` + renderSATCard("nvidia", "NVIDIA GPU", `
`) + renderSATCard("memory", "Memory", "") + renderSATCard("storage", "Storage", "") + renderSATCard("cpu", "CPU", `
`) + renderSATCard("amd", "AMD GPU", "") + `
` } func renderSATCard(id, label, extra string) string { return fmt.Sprintf(`
%s
%s
`, label, extra, id, id) } // ── Burn ────────────────────────────────────────────────────────────────────── func renderBurn() string { return `
⚠ Warning: Stress tests on this page run hardware at maximum load. Repeated or prolonged use may reduce hardware lifespan (storage endurance, GPU wear). Use only when necessary.

Tasks continue in the background — view progress in Tasks.

Burn Profile

Applied to all tests on this page. NVIDIA uses mapped DCGM levels: smoke=quick, acceptance=targeted stress, overnight=extended stress.

NVIDIA GPU Stress
CPU Stress
AMD GPU Stress

Runs ROCm compute stress together with VRAM copy/load activity via RVS GST and records a separate rocm-bandwidth-test snapshot. Missing tools reported as UNSUPPORTED.

Memory Stress

stress-ng --vm writes and verifies memory patterns across all of RAM. Env: BEE_VM_STRESS_SECONDS (default 300), BEE_VM_STRESS_SIZE_MB (default 80%).

SAT Stress (stressapptest)

Google stressapptest saturates CPU, memory and cache buses simultaneously. Env: BEE_SAT_STRESS_SECONDS (default 300), BEE_SAT_STRESS_MB (default auto).

` } // ── Network ─────────────────────────────────────────────────────────────────── // renderNetworkInline returns the network UI without a wrapping card (for embedding in Tools). func renderNetworkInline() string { return `

Loading...

DHCP
Static IPv4
` } func renderNetwork() string { return `
Network Interfaces
` + renderNetworkInline() + `
` } // ── Services ────────────────────────────────────────────────────────────────── func renderServicesInline() string { return `

Loading...

` } func renderServices() string { return `
Bee Services
` + renderServicesInline() + `
` } // ── Export ──────────────────────────────────────────────────────────────────── func renderExport(exportDir string) string { entries, _ := listExportFiles(exportDir) var rows strings.Builder for _, e := range entries { rows.WriteString(fmt.Sprintf(`%s`, url.QueryEscape(e), html.EscapeString(e))) } if len(entries) == 0 { rows.WriteString(`No export files found.`) } return `
Support Bundle

Creates a tar.gz archive of all audit files, SAT results, and logs.

⬇ Download Support Bundle
Export Files
` + rows.String() + `
File
` } func listExportFiles(exportDir string) ([]string, error) { var entries []string err := filepath.Walk(strings.TrimSpace(exportDir), func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } rel, err := filepath.Rel(exportDir, path) if err != nil { return err } entries = append(entries, rel) return nil }) if err != nil && !os.IsNotExist(err) { return nil, err } sort.Strings(entries) return entries, nil } // ── Tools ───────────────────────────────────────────────────────────────────── func renderTools() string { return `
System Install
Install to RAM

Checking...

Install to Disk
` + renderInstallInline() + `
Support Bundle

Downloads a tar.gz archive of all audit files, SAT results, and logs.

↓ Download Support Bundle
Tool Check

Checking...

Network
` + renderNetworkInline() + `
Services
` + renderServicesInline() + `
` } // ── Install to Disk ────────────────────────────────────────────────────────── func renderInstallInline() string { return `
Warning: Installing will completely erase the selected disk and write the live system onto it. All existing data on the target disk will be lost. This operation cannot be undone.
Loading disk list…
` } func renderInstall() string { return `
Install Live System to Disk
` + renderInstallInline() + `
` } // ── Tasks ───────────────────────────────────────────────────────────────────── func renderTasks() string { return `
Tasks run one at a time. Logs persist after navigation.

Loading...

` } func renderExportIndex(exportDir string) (string, error) { entries, err := listExportFiles(exportDir) if err != nil { return "", err } var body strings.Builder body.WriteString(`Bee Export Files`) body.WriteString(`

Bee Export Files

`) return body.String(), nil }