From 025548ab3c48aa659b0936df6d9cab3c63eaf27d Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Wed, 8 Apr 2026 21:37:01 +0300 Subject: [PATCH] UI: amber accents, smaller wallpaper logo, new support bundle name, drop display resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bootloader: GRUB fallback text colors → yellow/brown (amber tone) - CLI charts: all GPU metric series use single amber color (xterm-256 #214) - Wallpaper: logo width scaled to 400 px dynamically, shadow scales with font size - Support bundle: renamed to YYYY-MM-DD (BEE-SP vX.X) SRV_MODEL SRV_SN ToD.tar.gz using dmidecode for server model (spaces→underscores) and serial number - Remove display resolution feature (UI card, API routes, handlers, tests) Co-Authored-By: Claude Sonnet 4.6 --- audit/internal/app/support_bundle.go | 70 +++++++++++- audit/internal/platform/gpu_metrics.go | 13 +-- audit/internal/webui/api.go | 104 ------------------ audit/internal/webui/api_test.go | 24 ---- audit/internal/webui/pages.go | 51 --------- audit/internal/webui/server.go | 4 - .../config/bootloaders/grub-pc/theme.cfg | 6 +- .../hooks/normal/9001-wallpaper.hook.chroot | 18 ++- 8 files changed, 85 insertions(+), 205 deletions(-) diff --git a/audit/internal/app/support_bundle.go b/audit/internal/app/support_bundle.go index c735279..4454367 100644 --- a/audit/internal/app/support_bundle.go +++ b/audit/internal/app/support_bundle.go @@ -192,7 +192,7 @@ var supportBundleOptionalFiles = []struct { {name: "system/syslog.txt", src: "/var/log/syslog"}, } -const supportBundleGlob = "bee-support-*.tar.gz" +const supportBundleGlob = "????-??-?? (BEE-SP*)*.tar.gz" func BuildSupportBundle(exportDir string) (string, error) { exportDir = strings.TrimSpace(exportDir) @@ -206,9 +206,14 @@ func BuildSupportBundle(exportDir string) (string, error) { return "", err } - host := sanitizeFilename(hostnameOr("unknown")) - ts := time.Now().UTC().Format("20060102-150405") - stageRoot := filepath.Join(os.TempDir(), fmt.Sprintf("bee-support-%s-%s", host, ts)) + now := time.Now().UTC() + date := now.Format("2006-01-02") + tod := now.Format("15:04:05") + ver := bundleVersion() + model := serverModelForBundle() + sn := serverSerialForBundle() + + stageRoot := filepath.Join(os.TempDir(), fmt.Sprintf("bee-support-stage-%s-%s", sanitizeFilename(hostnameOr("unknown")), now.Format("20060102-150405"))) if err := os.MkdirAll(stageRoot, 0755); err != nil { return "", err } @@ -240,7 +245,8 @@ func BuildSupportBundle(exportDir string) (string, error) { return "", err } - archivePath := filepath.Join(os.TempDir(), fmt.Sprintf("bee-support-%s-%s.tar.gz", host, ts)) + archiveName := fmt.Sprintf("%s (BEE-SP v%s) %s %s %s.tar.gz", date, ver, model, sn, tod) + archivePath := filepath.Join(os.TempDir(), archiveName) if err := createSupportTarGz(archivePath, stageRoot); err != nil { return "", err } @@ -397,6 +403,60 @@ func writeManifest(dst, exportDir, stageRoot string) error { return os.WriteFile(dst, []byte(body.String()), 0644) } +func bundleVersion() string { + v := buildVersion() + v = strings.TrimPrefix(v, "v") + v = strings.TrimPrefix(v, "V") + if v == "" || v == "unknown" { + return "0.0" + } + return v +} + +func serverModelForBundle() string { + raw, err := exec.Command("dmidecode", "-t", "1").Output() + if err != nil { + return "unknown" + } + for _, line := range strings.Split(string(raw), "\n") { + line = strings.TrimSpace(line) + key, val, ok := strings.Cut(line, ": ") + if !ok { + continue + } + if strings.TrimSpace(key) == "Product Name" { + val = strings.TrimSpace(val) + if val == "" { + return "unknown" + } + return strings.ReplaceAll(val, " ", "_") + } + } + return "unknown" +} + +func serverSerialForBundle() string { + raw, err := exec.Command("dmidecode", "-t", "1").Output() + if err != nil { + return "unknown" + } + for _, line := range strings.Split(string(raw), "\n") { + line = strings.TrimSpace(line) + key, val, ok := strings.Cut(line, ": ") + if !ok { + continue + } + if strings.TrimSpace(key) == "Serial Number" { + val = strings.TrimSpace(val) + if val == "" { + return "unknown" + } + return val + } + } + return "unknown" +} + func buildVersion() string { raw, err := exec.Command("bee", "version").CombinedOutput() if err != nil { diff --git a/audit/internal/platform/gpu_metrics.go b/audit/internal/platform/gpu_metrics.go index be055d5..b22f438 100644 --- a/audit/internal/platform/gpu_metrics.go +++ b/audit/internal/platform/gpu_metrics.go @@ -383,10 +383,7 @@ func drawGPUChartSVG(rows []GPUMetricRow, gpuIdx int) string { } const ( - ansiRed = "\033[31m" - ansiBlue = "\033[34m" - ansiGreen = "\033[32m" - ansiYellow = "\033[33m" + ansiAmber = "\033[38;5;214m" ansiReset = "\033[0m" ) @@ -415,10 +412,10 @@ func RenderGPUTerminalChart(rows []GPUMetricRow) string { fn func(GPUMetricRow) float64 } defs := []seriesDef{ - {"Temperature (°C)", ansiRed, func(r GPUMetricRow) float64 { return r.TempC }}, - {"GPU Usage (%)", ansiBlue, func(r GPUMetricRow) float64 { return r.UsagePct }}, - {"Power (W)", ansiGreen, func(r GPUMetricRow) float64 { return r.PowerW }}, - {"Clock (MHz)", ansiYellow, func(r GPUMetricRow) float64 { return r.ClockMHz }}, + {"Temperature (°C)", ansiAmber, func(r GPUMetricRow) float64 { return r.TempC }}, + {"GPU Usage (%)", ansiAmber, func(r GPUMetricRow) float64 { return r.UsagePct }}, + {"Power (W)", ansiAmber, func(r GPUMetricRow) float64 { return r.PowerW }}, + {"Clock (MHz)", ansiAmber, func(r GPUMetricRow) float64 { return r.ClockMHz }}, } var b strings.Builder diff --git a/audit/internal/webui/api.go b/audit/internal/webui/api.go index 3d9ff94..419c7df 100644 --- a/audit/internal/webui/api.go +++ b/audit/internal/webui/api.go @@ -1376,107 +1376,3 @@ func (h *handler) rollbackPendingNetworkChange() error { return nil } -// ── Display / Screen Resolution ─────────────────────────────────────────────── - -type displayMode struct { - Output string `json:"output"` - Mode string `json:"mode"` - Current bool `json:"current"` -} - -type displayInfo struct { - Output string `json:"output"` - Modes []displayMode `json:"modes"` - Current string `json:"current"` -} - -var xrandrOutputRE = regexp.MustCompile(`^(\S+)\s+connected`) -var xrandrModeRE = regexp.MustCompile(`^\s{3}(\d+x\d+)\s`) -var xrandrCurrentRE = regexp.MustCompile(`\*`) - -func parseXrandrOutput(out string) []displayInfo { - var infos []displayInfo - var cur *displayInfo - for _, line := range strings.Split(out, "\n") { - if m := xrandrOutputRE.FindStringSubmatch(line); m != nil { - if cur != nil { - infos = append(infos, *cur) - } - cur = &displayInfo{Output: m[1]} - continue - } - if cur == nil { - continue - } - if m := xrandrModeRE.FindStringSubmatch(line); m != nil { - isCurrent := xrandrCurrentRE.MatchString(line) - mode := displayMode{Output: cur.Output, Mode: m[1], Current: isCurrent} - cur.Modes = append(cur.Modes, mode) - if isCurrent { - cur.Current = m[1] - } - } - } - if cur != nil { - infos = append(infos, *cur) - } - return infos -} - -func xrandrCommand(args ...string) *exec.Cmd { - cmd := exec.Command("xrandr", args...) - env := append([]string{}, os.Environ()...) - hasDisplay := false - hasXAuthority := false - for _, kv := range env { - if strings.HasPrefix(kv, "DISPLAY=") && strings.TrimPrefix(kv, "DISPLAY=") != "" { - hasDisplay = true - } - if strings.HasPrefix(kv, "XAUTHORITY=") && strings.TrimPrefix(kv, "XAUTHORITY=") != "" { - hasXAuthority = true - } - } - if !hasDisplay { - env = append(env, "DISPLAY=:0") - } - if !hasXAuthority { - env = append(env, "XAUTHORITY=/home/bee/.Xauthority") - } - cmd.Env = env - return cmd -} - -func (h *handler) handleAPIDisplayResolutions(w http.ResponseWriter, _ *http.Request) { - out, err := xrandrCommand().Output() - if err != nil { - writeError(w, http.StatusInternalServerError, "xrandr: "+err.Error()) - return - } - writeJSON(w, parseXrandrOutput(string(out))) -} - -func (h *handler) handleAPIDisplaySet(w http.ResponseWriter, r *http.Request) { - var req struct { - Output string `json:"output"` - Mode string `json:"mode"` - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Output == "" || req.Mode == "" { - writeError(w, http.StatusBadRequest, "output and mode are required") - return - } - // Validate mode looks like WxH to prevent injection - if !regexp.MustCompile(`^\d+x\d+$`).MatchString(req.Mode) { - writeError(w, http.StatusBadRequest, "invalid mode format") - return - } - // Validate output name (no special chars) - if !regexp.MustCompile(`^[A-Za-z0-9_\-]+$`).MatchString(req.Output) { - writeError(w, http.StatusBadRequest, "invalid output name") - return - } - if out, err := xrandrCommand("--output", req.Output, "--mode", req.Mode).CombinedOutput(); err != nil { - writeError(w, http.StatusInternalServerError, "xrandr: "+strings.TrimSpace(string(out))) - return - } - writeJSON(w, map[string]string{"status": "ok", "output": req.Output, "mode": req.Mode}) -} diff --git a/audit/internal/webui/api_test.go b/audit/internal/webui/api_test.go index a781dc0..124090f 100644 --- a/audit/internal/webui/api_test.go +++ b/audit/internal/webui/api_test.go @@ -10,30 +10,6 @@ import ( "bee/audit/internal/platform" ) -func TestXrandrCommandAddsDefaultX11Env(t *testing.T) { - t.Setenv("DISPLAY", "") - t.Setenv("XAUTHORITY", "") - - cmd := xrandrCommand("--query") - - var hasDisplay bool - var hasXAuthority bool - for _, kv := range cmd.Env { - if kv == "DISPLAY=:0" { - hasDisplay = true - } - if kv == "XAUTHORITY=/home/bee/.Xauthority" { - hasXAuthority = true - } - } - if !hasDisplay { - t.Fatalf("DISPLAY not injected: %v", cmd.Env) - } - if !hasXAuthority { - t.Fatalf("XAUTHORITY not injected: %v", cmd.Env) - } -} - func TestHandleAPISATRunDecodesBodyWithoutContentLength(t *testing.T) { globalQueue.mu.Lock() originalTasks := globalQueue.tasks diff --git a/audit/internal/webui/pages.go b/audit/internal/webui/pages.go index 9f867c8..ce7adde 100644 --- a/audit/internal/webui/pages.go +++ b/audit/internal/webui/pages.go @@ -2849,55 +2849,6 @@ usbRefresh(); ` } -// ── Display Resolution ──────────────────────────────────────────────────────── - -func renderDisplayInline() string { - return `
Loading displays...
-
-` -} func renderNvidiaSelfHealInline() string { return `

Inspect NVIDIA GPU health, restart the bee-nvidia driver service, and issue a per-GPU reset when the driver reports reset required.

@@ -3086,8 +3037,6 @@ function installToRAM() {
Services
` + renderServicesInline() + `
-
Display Resolution
` + - renderDisplayInline() + `