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) string { items := []struct{ id, label, href, onclick string }{ {"dashboard", "Dashboard", "/", ""}, {"audit", "Audit", "#", "openAuditModal();return false;"}, {"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 "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) + `

` + 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 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
Server — Temperature
CPU temperature
Server — Power
System power
` } // ── 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.

NVIDIA GPU Stress
CPU Stress
AMD GPU Stress

Requires ROCm tools (rocm-bandwidth-test). 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 `
Install to RAM

Checking...

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
` + renderInstallInline() + `
` } // ── 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 }