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(renderAuditStatusBanner(opts)) b.WriteString(renderHardwareSummaryCard(opts)) b.WriteString(renderHealthCard(opts)) b.WriteString(renderMetrics()) 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 { data, err := loadSnapshot(opts.AuditPath) if err != nil { return `
Hardware Summary
` } // 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", `

Additional AMD memory diagnostics: RVS MEM for integrity and BABEL + rocm-bandwidth-test for memory/interconnect bandwidth.

`) + `
` } 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
GPU Stress

Tests run on all GPUs in the system. Availability determined by driver status.

Compute Stress

Select which subsystems to stress. Each checked item runs as a separate task.

Platform Thermal Cycling

Repeated load+idle cycles. Detects cooling recovery failures and GPU throttle. Smoke: 2×90s. Acceptance: 4×300s.

Load components:

` } // ── 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.

` + renderSupportBundleInline() + `
Export Files
` + rows.String() + `
File
` + renderUSBExportCard() } 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 } func renderSupportBundleInline() string { return `
` } func renderUSBExportCard() string { return `
Export to USB
` + renderUSBExportInline() + `
` } func renderUSBExportInline() string { return `

Write audit JSON or support bundle directly to a removable USB drive.

Scanning for USB devices...
` } // ── Display Resolution ──────────────────────────────────────────────────────── func renderDisplayInline() string { return `
Loading displays...
` } // ── Tools ───────────────────────────────────────────────────────────────────── func renderTools() string { return `
System Install
Install to RAM

Detecting boot source...

Checking...

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

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

` + renderSupportBundleInline() + `
Export to USB
` + renderUSBExportInline() + `
Tool Check

Checking...

Network
` + renderNetworkInline() + `
Services
` + renderServicesInline() + `
Display Resolution
` + renderDisplayInline() + `
` } // ── 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 }