From b37b34ea6ad906f54e8104526e77ba66aed997da Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 9 Mar 2026 17:43:08 +0100 Subject: Dashboard overhaul, compute integration (#814) - **Frontend dashboard overhaul**: Unified compute/main dashboards into a single shared UI. Added new pages for cache, projects, metrics, sessions, info (build/runtime config, system stats). Added live-update via WebSockets with pause control, sortable detail tables, themed styling. Refactored compute/hub/orchestrator pages into modular JS. - **HTTP server fixes and stats**: Fixed http.sys local-only fallback when default port is in use, implemented root endpoint redirect for http.sys, fixed Linux/Mac port reuse. Added /stats endpoint exposing HTTP server metrics (bytes transferred, request rates). Added WebSocket stats tracking. - **OTEL/diagnostics hardening**: Improved OTLP HTTP exporter with better error handling and resilience. Extended diagnostics services configuration. - **Session management**: Added new sessions service with HTTP endpoints for registering, updating, querying, and removing sessions. Includes session log file support. This is still WIP. - **CLI subcommand support**: Added support for commands with subcommands in the zen CLI tool, with improved command dispatch. - **Misc**: Exposed CPU usage/hostname to frontend, fixed JS compact binary float32/float64 decoding, limited projects displayed on front page to 25 sorted by last access, added vscode:// link support. Also contains some fixes from TSAN analysis. --- src/zenserver/frontend/html/theme.js | 116 +++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/zenserver/frontend/html/theme.js (limited to 'src/zenserver/frontend/html/theme.js') diff --git a/src/zenserver/frontend/html/theme.js b/src/zenserver/frontend/html/theme.js new file mode 100644 index 000000000..52ca116ab --- /dev/null +++ b/src/zenserver/frontend/html/theme.js @@ -0,0 +1,116 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +// Theme toggle: cycles system → light → dark → system. +// Persists choice in localStorage. Applies data-theme attribute on . + +(function() { + var KEY = 'zen-theme'; + + function getStored() { + try { return localStorage.getItem(KEY); } catch (e) { return null; } + } + + function setStored(value) { + try { + if (value) localStorage.setItem(KEY, value); + else localStorage.removeItem(KEY); + } catch (e) {} + } + + function apply(theme) { + if (theme) + document.documentElement.setAttribute('data-theme', theme); + else + document.documentElement.removeAttribute('data-theme'); + } + + function getEffective(stored) { + if (stored) return stored; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + + // Apply stored preference immediately (before paint) + var stored = getStored(); + apply(stored); + + // Create toggle button once DOM is ready + function createToggle() { + var btn = document.createElement('button'); + btn.id = 'zen_theme_toggle'; + btn.title = 'Toggle theme'; + + function updateIcon() { + var effective = getEffective(getStored()); + // Show sun in dark mode (click to go light), moon in light mode (click to go dark) + btn.textContent = effective === 'dark' ? '\u2600' : '\u263E'; + + var isManual = getStored() != null; + btn.title = isManual + ? 'Theme: ' + effective + ' (click to change, double-click for system)' + : 'Theme: system (click to change)'; + } + + btn.addEventListener('click', function() { + var current = getStored(); + var effective = getEffective(current); + // Toggle to the opposite + var next = effective === 'dark' ? 'light' : 'dark'; + setStored(next); + apply(next); + updateIcon(); + }); + + btn.addEventListener('dblclick', function(e) { + e.preventDefault(); + // Reset to system preference + setStored(null); + apply(null); + updateIcon(); + }); + + // Update icon when system preference changes + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() { + if (!getStored()) updateIcon(); + }); + + updateIcon(); + document.body.appendChild(btn); + + // WebSocket pause/play toggle + var WS_KEY = 'zen-ws-paused'; + var wsBtn = document.createElement('button'); + wsBtn.id = 'zen_ws_toggle'; + + var initialPaused = false; + try { initialPaused = localStorage.getItem(WS_KEY) === 'true'; } catch (e) {} + + function updateWsIcon(paused) { + wsBtn.dataset.paused = paused ? 'true' : 'false'; + wsBtn.textContent = paused ? '\u25B6' : '\u23F8'; + wsBtn.title = paused ? 'Resume live updates' : 'Pause live updates'; + } + + updateWsIcon(initialPaused); + + // Fire initial event so pages pick up persisted state + document.addEventListener('DOMContentLoaded', function() { + if (initialPaused) { + document.dispatchEvent(new CustomEvent('zen-ws-toggle', { detail: { paused: true } })); + } + }); + + wsBtn.addEventListener('click', function() { + var paused = wsBtn.dataset.paused !== 'true'; + try { localStorage.setItem(WS_KEY, paused ? 'true' : 'false'); } catch (e) {} + updateWsIcon(paused); + document.dispatchEvent(new CustomEvent('zen-ws-toggle', { detail: { paused: paused } })); + }); + + document.body.appendChild(wsBtn); + } + + if (document.readyState === 'loading') + document.addEventListener('DOMContentLoaded', createToggle); + else + createToggle(); +})(); -- cgit v1.2.3