diff options
| author | Stefan Boberg <[email protected]> | 2026-03-09 17:43:08 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-09 17:43:08 +0100 |
| commit | b37b34ea6ad906f54e8104526e77ba66aed997da (patch) | |
| tree | e80ce17d666aff6d2f0d73d4977128ffb4055476 /src/zenserver/frontend/html/compute | |
| parent | add fallback for zencache multirange (#816) (diff) | |
| download | zen-b37b34ea6ad906f54e8104526e77ba66aed997da.tar.xz zen-b37b34ea6ad906f54e8104526e77ba66aed997da.zip | |
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.
Diffstat (limited to 'src/zenserver/frontend/html/compute')
| -rw-r--r-- | src/zenserver/frontend/html/compute/banner.js | 321 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/compute/compute.html | 327 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/compute/hub.html | 154 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/compute/nav.js | 79 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/compute/orchestrator.html | 205 |
5 files changed, 123 insertions, 963 deletions
diff --git a/src/zenserver/frontend/html/compute/banner.js b/src/zenserver/frontend/html/compute/banner.js deleted file mode 100644 index 61c7ce21f..000000000 --- a/src/zenserver/frontend/html/compute/banner.js +++ /dev/null @@ -1,321 +0,0 @@ -/** - * zen-banner.js — Zen Compute dashboard banner Web Component - * - * Usage: - * <script src="/components/zen-banner.js" defer></script> - * - * <zen-banner></zen-banner> - * <zen-banner variant="compact"></zen-banner> - * <zen-banner cluster-status="degraded" load="78"></zen-banner> - * - * Attributes: - * variant "full" (default) | "compact" - * cluster-status "nominal" (default) | "degraded" | "offline" - * load 0–100 integer, shown as a percentage (default: hidden) - * tagline custom tagline text (default: "Orchestrator Overview" / "Orchestrator") - * subtitle text after "ZEN" in the wordmark (default: "COMPUTE") - */ - -class ZenBanner extends HTMLElement { - - static get observedAttributes() { - return ['variant', 'cluster-status', 'load', 'tagline', 'subtitle']; - } - - attributeChangedCallback() { - if (this.shadowRoot) this._render(); - } - - connectedCallback() { - if (!this.shadowRoot) this.attachShadow({ mode: 'open' }); - this._render(); - } - - // ───────────────────────────────────────────── - // Derived values - // ───────────────────────────────────────────── - - get _variant() { return this.getAttribute('variant') || 'full'; } - get _status() { return (this.getAttribute('cluster-status') || 'nominal').toLowerCase(); } - get _load() { return this.getAttribute('load'); } // null → hidden - get _tagline() { return this.getAttribute('tagline'); } // null → default - get _subtitle() { return this.getAttribute('subtitle'); } // null → "COMPUTE" - - get _statusColor() { - return { nominal: '#7ecfb8', degraded: '#d4a84b', offline: '#c0504d' }[this._status] ?? '#7ecfb8'; - } - - get _statusLabel() { - return { nominal: 'NOMINAL', degraded: 'DEGRADED', offline: 'OFFLINE' }[this._status] ?? 'NOMINAL'; - } - - get _loadColor() { - const v = parseInt(this._load, 10); - if (isNaN(v)) return '#7ecfb8'; - if (v >= 85) return '#c0504d'; - if (v >= 60) return '#d4a84b'; - return '#7ecfb8'; - } - - // ───────────────────────────────────────────── - // Render - // ───────────────────────────────────────────── - - _render() { - const compact = this._variant === 'compact'; - this.shadowRoot.innerHTML = ` - <style>${this._css(compact)}</style> - ${this._html(compact)} - `; - } - - // ───────────────────────────────────────────── - // CSS - // ───────────────────────────────────────────── - - _css(compact) { - const height = compact ? '60px' : '100px'; - const padding = compact ? '0 24px' : '0 32px'; - const gap = compact ? '16px' : '24px'; - const markSize = compact ? '34px' : '52px'; - const divH = compact ? '32px' : '48px'; - const nameSize = compact ? '15px' : '22px'; - const tagSize = compact ? '9px' : '11px'; - const sc = this._statusColor; - const lc = this._loadColor; - - return ` - @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+JP:wght@300;400&family=Space+Mono:wght@400;700&display=swap'); - - *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - - :host { - display: block; - font-family: 'Space Mono', monospace; - } - - .banner { - width: 100%; - height: ${height}; - background: #0b0d10; - border: 1px solid #1e2330; - border-radius: 6px; - display: flex; - align-items: center; - padding: ${padding}; - gap: ${gap}; - position: relative; - overflow: hidden; - } - - /* scan-line texture */ - .banner::before { - content: ''; - position: absolute; - inset: 0; - background: repeating-linear-gradient( - 0deg, - transparent, transparent 3px, - rgba(255,255,255,0.012) 3px, rgba(255,255,255,0.012) 4px - ); - pointer-events: none; - } - - /* ambient glow */ - .banner::after { - content: ''; - position: absolute; - right: -60px; - top: 50%; - transform: translateY(-50%); - width: 280px; - height: 280px; - background: radial-gradient(circle, rgba(130,200,180,0.06) 0%, transparent 70%); - pointer-events: none; - } - - .logo-mark { - flex-shrink: 0; - width: ${markSize}; - height: ${markSize}; - } - - .logo-mark svg { width: 100%; height: 100%; } - - .divider { - width: 1px; - height: ${divH}; - background: linear-gradient(to bottom, transparent, #2a3040, transparent); - flex-shrink: 0; - } - - .text-block { - display: flex; - flex-direction: column; - gap: 4px; - } - - .wordmark { - font-weight: 700; - font-size: ${nameSize}; - letter-spacing: 0.12em; - color: #e8e4dc; - text-transform: uppercase; - line-height: 1; - } - - .wordmark span { color: #7ecfb8; } - - .tagline { - font-family: 'Noto Serif JP', serif; - font-weight: 300; - font-size: ${tagSize}; - letter-spacing: 0.3em; - color: #4a5a68; - text-transform: uppercase; - } - - .spacer { flex: 1; } - - /* ── right-side decorative circuit ── */ - .circuit { flex-shrink: 0; opacity: 0.22; } - - /* ── status cluster ── */ - .status-cluster { - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 6px; - } - - .status-row { - display: flex; - align-items: center; - gap: 8px; - } - - .status-lbl { - font-size: 9px; - letter-spacing: 0.18em; - color: #3a4555; - text-transform: uppercase; - } - - .pill { - display: flex; - align-items: center; - gap: 5px; - border-radius: 20px; - padding: 2px 10px; - font-size: 10px; - letter-spacing: 0.1em; - } - - .pill.cluster { - color: ${sc}; - background: color-mix(in srgb, ${sc} 8%, transparent); - border: 1px solid color-mix(in srgb, ${sc} 28%, transparent); - } - - .pill.load-pill { - color: ${lc}; - background: color-mix(in srgb, ${lc} 8%, transparent); - border: 1px solid color-mix(in srgb, ${lc} 28%, transparent); - } - - .dot { - width: 5px; - height: 5px; - border-radius: 50%; - animation: pulse 2.4s ease-in-out infinite; - } - - .dot.cluster { background: ${sc}; } - .dot.load-dot { background: ${lc}; animation-delay: 0.5s; } - - @keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.25; } - } - `; - } - - // ───────────────────────────────────────────── - // HTML template - // ───────────────────────────────────────────── - - _html(compact) { - const loadAttr = this._load; - const showStatus = !compact; - - const rightSide = showStatus ? ` - <svg class="circuit" width="60" height="60" viewBox="0 0 60 60" fill="none"> - <path d="M5 30 H22 L28 18 H60" stroke="#7ecfb8" stroke-width="0.8"/> - <path d="M5 38 H18 L24 46 H60" stroke="#7ecfb8" stroke-width="0.8"/> - <circle cx="22" cy="30" r="2" fill="none" stroke="#7ecfb8" stroke-width="0.8"/> - <circle cx="18" cy="38" r="2" fill="none" stroke="#7ecfb8" stroke-width="0.8"/> - <circle cx="10" cy="30" r="1.2" fill="#7ecfb8"/> - <circle cx="10" cy="38" r="1.2" fill="#7ecfb8"/> - </svg> - - <div class="status-cluster"> - <div class="status-row"> - <span class="status-lbl">Cluster</span> - <div class="pill cluster"> - <div class="dot cluster"></div> - ${this._statusLabel} - </div> - </div> - ${loadAttr !== null ? ` - <div class="status-row"> - <span class="status-lbl">Load</span> - <div class="pill load-pill"> - <div class="dot load-dot"></div> - ${parseInt(loadAttr, 10)} % - </div> - </div>` : ''} - </div> - ` : ''; - - return ` - <div class="banner"> - <div class="logo-mark">${this._svgMark()}</div> - <div class="divider"></div> - <div class="text-block"> - <div class="wordmark">ZEN<span> ${this._subtitle ?? 'COMPUTE'}</span></div> - <div class="tagline">${this._tagline ?? (compact ? 'Orchestrator' : 'Orchestrator Overview')}</div> - </div> - <div class="spacer"></div> - ${rightSide} - </div> - `; - } - - // ───────────────────────────────────────────── - // SVG logo mark - // ───────────────────────────────────────────── - - _svgMark() { - return ` - <svg viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg"> - <circle cx="26" cy="26" r="22" stroke="#2a3a48" stroke-width="1.5"/> - <path d="M26 4 A22 22 0 1 1 12 43.1" stroke="#7ecfb8" stroke-width="2" stroke-linecap="round" fill="none"/> - <circle cx="17" cy="17" r="1.6" fill="#7ecfb8" /> - <circle cx="26" cy="17" r="1.6" fill="#7ecfb8" /> - <circle cx="35" cy="17" r="1.6" fill="#7ecfb8" /> - <circle cx="17" cy="26" r="1.6" fill="#7ecfb8" opacity="0.6"/> - <circle cx="26" cy="26" r="2.2" fill="#7ecfb8"/> - <circle cx="35" cy="26" r="1.6" fill="#7ecfb8" opacity="0.6"/> - <circle cx="17" cy="35" r="1.6" fill="#7ecfb8"/> - <circle cx="26" cy="35" r="1.6" fill="#7ecfb8"/> - <circle cx="35" cy="35" r="1.6" fill="#7ecfb8"/> - <line x1="17" y1="17" x2="35" y2="17" stroke="#7ecfb8" stroke-width="0.7" stroke-opacity="0.25"/> - <line x1="35" y1="17" x2="17" y2="35" stroke="#7ecfb8" stroke-width="0.7" stroke-opacity="0.25"/> - <line x1="17" y1="35" x2="35" y2="35" stroke="#7ecfb8" stroke-width="0.7" stroke-opacity="0.2"/> - <line x1="26" y1="17" x2="26" y2="35" stroke="#7ecfb8" stroke-width="0.7" stroke-opacity="0.2"/> - </svg> - `; - } -} - -customElements.define('zen-banner', ZenBanner); diff --git a/src/zenserver/frontend/html/compute/compute.html b/src/zenserver/frontend/html/compute/compute.html index 1e101d839..66c20175f 100644 --- a/src/zenserver/frontend/html/compute/compute.html +++ b/src/zenserver/frontend/html/compute/compute.html @@ -5,101 +5,13 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Zen Compute Dashboard</title> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script> - <script src="banner.js" defer></script> - <script src="nav.js" defer></script> + <link rel="stylesheet" type="text/css" href="../zen.css" /> + <script src="../theme.js"></script> + <script src="../banner.js" defer></script> + <script src="../nav.js" defer></script> <style> - * { - margin: 0; - padding: 0; - box-sizing: border-box; - } - - body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - background: #0d1117; - color: #c9d1d9; - padding: 20px; - } - - .container { - max-width: 1400px; - margin: 0 auto; - } - - h1 { - font-size: 32px; - font-weight: 600; - margin-bottom: 10px; - color: #f0f6fc; - } - - .header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 30px; - } - - .health-indicator { - display: flex; - align-items: center; - gap: 8px; - font-size: 14px; - padding: 8px 16px; - border-radius: 6px; - background: #161b22; - border: 1px solid #30363d; - } - - .health-indicator .status-dot { - width: 10px; - height: 10px; - border-radius: 50%; - background: #6e7681; - } - - .health-indicator.healthy .status-dot { - background: #3fb950; - } - - .health-indicator.unhealthy .status-dot { - background: #f85149; - } - .grid { - display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 20px; - margin-bottom: 30px; - } - - .card { - background: #161b22; - border: 1px solid #30363d; - border-radius: 6px; - padding: 20px; - } - - .card-title { - font-size: 14px; - font-weight: 600; - color: #8b949e; - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; - } - - .metric-value { - font-size: 36px; - font-weight: 600; - color: #f0f6fc; - line-height: 1; - } - - .metric-label { - font-size: 12px; - color: #8b949e; - margin-top: 4px; } .chart-container { @@ -113,7 +25,7 @@ justify-content: space-between; margin-bottom: 12px; padding: 8px 0; - border-bottom: 1px solid #21262d; + border-bottom: 1px solid var(--theme_border_subtle); } .stats-row:last-child { @@ -122,12 +34,12 @@ } .stats-label { - color: #8b949e; + color: var(--theme_g1); font-size: 13px; } .stats-value { - color: #f0f6fc; + color: var(--theme_bright); font-weight: 600; font-size: 13px; } @@ -146,77 +58,39 @@ .rate-value { font-size: 20px; font-weight: 600; - color: #58a6ff; + color: var(--theme_p0); } .rate-label { font-size: 11px; - color: #8b949e; + color: var(--theme_g1); margin-top: 4px; text-transform: uppercase; } - .progress-bar { - width: 100%; - height: 8px; - background: #21262d; - border-radius: 4px; - overflow: hidden; - margin-top: 8px; - } - - .progress-fill { - height: 100%; - background: #58a6ff; - transition: width 0.3s ease; - } - - .timestamp { - font-size: 12px; - color: #6e7681; - text-align: right; - margin-top: 30px; - } - - .error { - color: #f85149; - padding: 12px; - background: #1c1c1c; - border-radius: 6px; - margin: 20px 0; - font-size: 13px; - } - - .section-title { - font-size: 20px; - font-weight: 600; - margin-bottom: 20px; - color: #f0f6fc; - } - .worker-row { cursor: pointer; transition: background 0.15s; } .worker-row:hover { - background: #1c2128; + background: var(--theme_p4); } .worker-row.selected { - background: #1f2d3d; + background: var(--theme_p3); } .worker-detail { margin-top: 20px; - border-top: 1px solid #30363d; + border-top: 1px solid var(--theme_g2); padding-top: 16px; } .worker-detail-title { font-size: 15px; font-weight: 600; - color: #f0f6fc; + color: var(--theme_bright); margin-bottom: 12px; } @@ -227,7 +101,7 @@ .detail-section-label { font-size: 11px; font-weight: 600; - color: #8b949e; + color: var(--theme_g1); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; @@ -241,13 +115,13 @@ .detail-table td { padding: 4px 8px; - color: #c9d1d9; - border-bottom: 1px solid #21262d; + color: var(--theme_g0); + border-bottom: 1px solid var(--theme_border_subtle); vertical-align: top; } .detail-table td:first-child { - color: #8b949e; + color: var(--theme_g1); width: 40%; font-family: monospace; } @@ -259,42 +133,25 @@ .detail-mono { font-family: monospace; font-size: 11px; - color: #8b949e; + color: var(--theme_g1); } .detail-tag { display: inline-block; padding: 2px 8px; border-radius: 4px; - background: #21262d; - color: #c9d1d9; + background: var(--theme_border_subtle); + color: var(--theme_g0); font-size: 11px; margin: 2px 4px 2px 0; } - - .status-badge { - display: inline-block; - padding: 2px 8px; - border-radius: 4px; - font-size: 11px; - font-weight: 600; - } - - .status-badge.success { - background: rgba(63, 185, 80, 0.15); - color: #3fb950; - } - - .status-badge.failure { - background: rgba(248, 81, 73, 0.15); - color: #f85149; - } </style> </head> <body> - <div class="container"> - <zen-banner cluster-status="nominal" load="0" tagline="Node Overview"></zen-banner> + <div class="container" style="max-width: 1400px; margin: 0 auto;"> + <zen-banner cluster-status="nominal" load="0" tagline="Node Overview" logo-src="../favicon.ico"></zen-banner> <zen-nav> + <a href="/dashboard/">Home</a> <a href="compute.html">Node</a> <a href="orchestrator.html">Orchestrator</a> </zen-nav> @@ -369,15 +226,15 @@ <span class="stats-value" id="worker-count">-</span> </div> <div id="worker-table-container" style="margin-top: 16px; display: none;"> - <table id="worker-table" style="width: 100%; border-collapse: collapse; font-size: 13px;"> + <table id="worker-table"> <thead> <tr> - <th style="text-align: left; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Name</th> - <th style="text-align: left; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Platform</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Cores</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Timeout</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Functions</th> - <th style="text-align: left; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Worker ID</th> + <th>Name</th> + <th>Platform</th> + <th style="text-align: right;">Cores</th> + <th style="text-align: right;">Timeout</th> + <th style="text-align: right;">Functions</th> + <th>Worker ID</th> </tr> </thead> <tbody id="worker-table-body"></tbody> @@ -390,19 +247,19 @@ <div class="section-title">Queues</div> <div class="card" style="margin-bottom: 30px;"> <div class="card-title">Queue Status</div> - <div id="queue-list-empty" style="color: #6e7681; font-size: 13px;">No queues.</div> + <div id="queue-list-empty" class="empty-state" style="text-align: left;">No queues.</div> <div id="queue-list-container" style="display: none;"> - <table id="queue-list-table" style="width: 100%; border-collapse: collapse; font-size: 13px;"> + <table id="queue-list-table"> <thead> <tr> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; width: 60px;">ID</th> - <th style="text-align: center; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; width: 80px;">Status</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Active</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Completed</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Failed</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Abandoned</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Cancelled</th> - <th style="text-align: left; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Token</th> + <th style="text-align: right; width: 60px;">ID</th> + <th style="text-align: center; width: 80px;">Status</th> + <th style="text-align: right;">Active</th> + <th style="text-align: right;">Completed</th> + <th style="text-align: right;">Failed</th> + <th style="text-align: right;">Abandoned</th> + <th style="text-align: right;">Cancelled</th> + <th>Token</th> </tr> </thead> <tbody id="queue-list-body"></tbody> @@ -414,20 +271,20 @@ <div class="section-title">Recent Actions</div> <div class="card" style="margin-bottom: 30px;"> <div class="card-title">Action History</div> - <div id="action-history-empty" style="color: #6e7681; font-size: 13px;">No actions recorded yet.</div> + <div id="action-history-empty" class="empty-state" style="text-align: left;">No actions recorded yet.</div> <div id="action-history-container" style="display: none;"> - <table id="action-history-table" style="width: 100%; border-collapse: collapse; font-size: 13px;"> + <table id="action-history-table"> <thead> <tr> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; width: 60px;">LSN</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; width: 60px;">Queue</th> - <th style="text-align: center; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; width: 70px;">Status</th> - <th style="text-align: left; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Function</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; width: 80px;">Started</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; width: 80px;">Finished</th> - <th style="text-align: right; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; width: 80px;">Duration</th> - <th style="text-align: left; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Worker ID</th> - <th style="text-align: left; color: #8b949e; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px;">Action ID</th> + <th style="text-align: right; width: 60px;">LSN</th> + <th style="text-align: right; width: 60px;">Queue</th> + <th style="text-align: center; width: 70px;">Status</th> + <th>Function</th> + <th style="text-align: right; width: 80px;">Started</th> + <th style="text-align: right; width: 80px;">Finished</th> + <th style="text-align: right; width: 80px;">Duration</th> + <th>Worker ID</th> + <th>Action ID</th> </tr> </thead> <tbody id="action-history-body"></tbody> @@ -766,7 +623,7 @@ // Functions const functions = desc.functions || []; - const functionsHtml = functions.length === 0 ? '<span style="color:#6e7681;font-size:12px;">none</span>' : + const functionsHtml = functions.length === 0 ? '<span style="color:var(--theme_faint);font-size:12px;">none</span>' : `<table class="detail-table">${functions.map(f => `<tr><td>${escapeHtml(f.name || '-')}</td><td class="detail-mono">${escapeHtml(f.version || '-')}</td></tr>` ).join('')}</table>`; @@ -774,12 +631,12 @@ // Executables const executables = desc.executables || []; const totalExecSize = executables.reduce((sum, e) => sum + (e.size || 0), 0); - const execHtml = executables.length === 0 ? '<span style="color:#6e7681;font-size:12px;">none</span>' : + const execHtml = executables.length === 0 ? '<span style="color:var(--theme_faint);font-size:12px;">none</span>' : `<table class="detail-table"> <tr style="font-size:11px;"> - <td style="color:#6e7681;padding-bottom:4px;">Path</td> - <td style="color:#6e7681;padding-bottom:4px;">Hash</td> - <td style="color:#6e7681;padding-bottom:4px;text-align:right;">Size</td> + <td style="color:var(--theme_faint);padding-bottom:4px;">Path</td> + <td style="color:var(--theme_faint);padding-bottom:4px;">Hash</td> + <td style="color:var(--theme_faint);padding-bottom:4px;text-align:right;">Size</td> </tr> ${executables.map(e => `<tr> @@ -788,28 +645,28 @@ <td style="text-align:right;white-space:nowrap;">${e.size != null ? formatBytes(e.size) : '-'}</td> </tr>` ).join('')} - <tr style="border-top:1px solid #30363d;"> - <td style="color:#8b949e;padding-top:6px;">Total</td> + <tr style="border-top:1px solid var(--theme_g2);"> + <td style="color:var(--theme_g1);padding-top:6px;">Total</td> <td></td> - <td style="text-align:right;white-space:nowrap;padding-top:6px;color:#f0f6fc;font-weight:600;">${formatBytes(totalExecSize)}</td> + <td style="text-align:right;white-space:nowrap;padding-top:6px;color:var(--theme_bright);font-weight:600;">${formatBytes(totalExecSize)}</td> </tr> </table>`; // Files const files = desc.files || []; - const filesHtml = files.length === 0 ? '<span style="color:#6e7681;font-size:12px;">none</span>' : + const filesHtml = files.length === 0 ? '<span style="color:var(--theme_faint);font-size:12px;">none</span>' : `<table class="detail-table">${files.map(f => `<tr><td>${escapeHtml(f.name || f)}</td><td class="detail-mono">${escapeHtml(f.hash || '')}</td></tr>` ).join('')}</table>`; // Dirs const dirs = desc.dirs || []; - const dirsHtml = dirs.length === 0 ? '<span style="color:#6e7681;font-size:12px;">none</span>' : + const dirsHtml = dirs.length === 0 ? '<span style="color:var(--theme_faint);font-size:12px;">none</span>' : dirs.map(d => `<span class="detail-tag">${escapeHtml(d)}</span>`).join(''); // Environment const env = desc.environment || []; - const envHtml = env.length === 0 ? '<span style="color:#6e7681;font-size:12px;">none</span>' : + const envHtml = env.length === 0 ? '<span style="color:var(--theme_faint);font-size:12px;">none</span>' : env.map(e => `<span class="detail-tag">${escapeHtml(e)}</span>`).join(''); panel.innerHTML = ` @@ -880,16 +737,16 @@ const timeout = desc ? (desc.timeout != null ? desc.timeout + 's' : '-') : '-'; const functions = desc ? (desc.functions ? desc.functions.length : 0) : '-'; - const tr = document.createElement('tr'); + const tr = document.createElement('tr'); tr.className = 'worker-row' + (id === selectedWorkerId ? ' selected' : ''); tr.dataset.workerId = id; tr.innerHTML = ` - <td style="padding: 6px 8px; color: #f0f6fc; border-bottom: 1px solid #21262d;">${escapeHtml(name)}</td> - <td style="padding: 6px 8px; color: #c9d1d9; border-bottom: 1px solid #21262d;">${escapeHtml(host)}</td> - <td style="padding: 6px 8px; color: #c9d1d9; border-bottom: 1px solid #21262d; text-align: right;">${escapeHtml(String(cores))}</td> - <td style="padding: 6px 8px; color: #c9d1d9; border-bottom: 1px solid #21262d; text-align: right;">${escapeHtml(String(timeout))}</td> - <td style="padding: 6px 8px; color: #c9d1d9; border-bottom: 1px solid #21262d; text-align: right;">${escapeHtml(String(functions))}</td> - <td style="padding: 6px 8px; color: #8b949e; border-bottom: 1px solid #21262d; font-family: monospace; font-size: 11px;">${escapeHtml(id)}</td> + <td style="color: var(--theme_bright);">${escapeHtml(name)}</td> + <td>${escapeHtml(host)}</td> + <td style="text-align: right;">${escapeHtml(String(cores))}</td> + <td style="text-align: right;">${escapeHtml(String(timeout))}</td> + <td style="text-align: right;">${escapeHtml(String(functions))}</td> + <td style="color: var(--theme_g1); font-family: monospace; font-size: 11px;">${escapeHtml(id)}</td> `; tr.addEventListener('click', () => { document.querySelectorAll('.worker-row').forEach(r => r.classList.remove('selected')); @@ -963,24 +820,24 @@ const badge = q.state === 'cancelled' ? '<span class="status-badge failure">cancelled</span>' : q.state === 'draining' - ? '<span class="status-badge" style="background:rgba(210,153,34,0.15);color:#d29922;">draining</span>' + ? '<span class="status-badge" style="background:color-mix(in srgb, var(--theme_warn) 15%, transparent);color:var(--theme_warn);">draining</span>' : q.is_complete ? '<span class="status-badge success">complete</span>' - : '<span class="status-badge" style="background:rgba(88,166,255,0.15);color:#58a6ff;">active</span>'; + : '<span class="status-badge" style="background:color-mix(in srgb, var(--theme_p0) 15%, transparent);color:var(--theme_p0);">active</span>'; const token = q.queue_token ? `<span class="detail-mono">${escapeHtml(q.queue_token)}</span>` - : '<span style="color:#6e7681;">-</span>'; + : '<span style="color:var(--theme_faint);">-</span>'; const tr = document.createElement('tr'); tr.innerHTML = ` - <td style="padding: 6px 8px; color: #f0f6fc; border-bottom: 1px solid #21262d; text-align: right; font-family: monospace;">${escapeHtml(String(id))}</td> - <td style="padding: 6px 8px; border-bottom: 1px solid #21262d; text-align: center;">${badge}</td> - <td style="padding: 6px 8px; color: #c9d1d9; border-bottom: 1px solid #21262d; text-align: right;">${q.active_count ?? 0}</td> - <td style="padding: 6px 8px; color: #3fb950; border-bottom: 1px solid #21262d; text-align: right;">${q.completed_count ?? 0}</td> - <td style="padding: 6px 8px; color: #f85149; border-bottom: 1px solid #21262d; text-align: right;">${q.failed_count ?? 0}</td> - <td style="padding: 6px 8px; color: #d29922; border-bottom: 1px solid #21262d; text-align: right;">${q.abandoned_count ?? 0}</td> - <td style="padding: 6px 8px; color: #f0883e; border-bottom: 1px solid #21262d; text-align: right;">${q.cancelled_count ?? 0}</td> - <td style="padding: 6px 8px; border-bottom: 1px solid #21262d;">${token}</td> + <td style="text-align: right; font-family: monospace; color: var(--theme_bright);">${escapeHtml(String(id))}</td> + <td style="text-align: center;">${badge}</td> + <td style="text-align: right;">${q.active_count ?? 0}</td> + <td style="text-align: right; color: var(--theme_ok);">${q.completed_count ?? 0}</td> + <td style="text-align: right; color: var(--theme_fail);">${q.failed_count ?? 0}</td> + <td style="text-align: right; color: var(--theme_warn);">${q.abandoned_count ?? 0}</td> + <td style="text-align: right; color: var(--theme_warn);">${q.cancelled_count ?? 0}</td> + <td>${token}</td> `; tbody.appendChild(tr); } @@ -1010,7 +867,7 @@ const lsn = entry.lsn ?? '-'; const succeeded = entry.succeeded; const badge = succeeded == null - ? '<span class="status-badge" style="background:#21262d;color:#8b949e;">unknown</span>' + ? '<span class="status-badge" style="background:var(--theme_border_subtle);color:var(--theme_g1);">unknown</span>' : succeeded ? '<span class="status-badge success">ok</span>' : '<span class="status-badge failure">failed</span>'; @@ -1024,20 +881,20 @@ const queueId = entry.queueId || 0; const queueCell = queueId - ? `<a href="/compute/queues/${queueId}" style="color: #58a6ff; text-decoration: none; font-family: monospace;">${escapeHtml(String(queueId))}</a>` - : '<span style="color: #6e7681;">-</span>'; + ? `<a href="/compute/queues/${queueId}" style="color: var(--theme_ln); text-decoration: none; font-family: monospace;">${escapeHtml(String(queueId))}</a>` + : '<span style="color: var(--theme_faint);">-</span>'; const tr = document.createElement('tr'); tr.innerHTML = ` - <td style="padding: 6px 8px; color: #8b949e; border-bottom: 1px solid #21262d; text-align: right; font-family: monospace;">${escapeHtml(String(lsn))}</td> - <td style="padding: 6px 8px; border-bottom: 1px solid #21262d; text-align: right;">${queueCell}</td> - <td style="padding: 6px 8px; border-bottom: 1px solid #21262d; text-align: center;">${badge}</td> - <td style="padding: 6px 8px; color: #f0f6fc; border-bottom: 1px solid #21262d;">${escapeHtml(fn)}</td> - <td style="padding: 6px 8px; color: #8b949e; border-bottom: 1px solid #21262d; text-align: right; font-size: 12px; white-space: nowrap;">${formatTime(startDate)}</td> - <td style="padding: 6px 8px; color: #8b949e; border-bottom: 1px solid #21262d; text-align: right; font-size: 12px; white-space: nowrap;">${formatTime(endDate)}</td> - <td style="padding: 6px 8px; color: #c9d1d9; border-bottom: 1px solid #21262d; text-align: right; font-size: 12px; white-space: nowrap;">${formatDuration(startDate, endDate)}</td> - <td style="padding: 6px 8px; color: #8b949e; border-bottom: 1px solid #21262d; font-family: monospace; font-size: 11px;">${escapeHtml(workerId)}</td> - <td style="padding: 6px 8px; color: #8b949e; border-bottom: 1px solid #21262d; font-family: monospace; font-size: 11px;">${escapeHtml(actionId)}</td> + <td style="text-align: right; font-family: monospace; color: var(--theme_g1);">${escapeHtml(String(lsn))}</td> + <td style="text-align: right;">${queueCell}</td> + <td style="text-align: center;">${badge}</td> + <td style="color: var(--theme_bright);">${escapeHtml(fn)}</td> + <td style="text-align: right; font-size: 12px; white-space: nowrap; color: var(--theme_g1);">${formatTime(startDate)}</td> + <td style="text-align: right; font-size: 12px; white-space: nowrap; color: var(--theme_g1);">${formatTime(endDate)}</td> + <td style="text-align: right; font-size: 12px; white-space: nowrap;">${formatDuration(startDate, endDate)}</td> + <td style="font-family: monospace; font-size: 11px; color: var(--theme_g1);">${escapeHtml(workerId)}</td> + <td style="font-family: monospace; font-size: 11px; color: var(--theme_g1);">${escapeHtml(actionId)}</td> `; tbody.appendChild(tr); } diff --git a/src/zenserver/frontend/html/compute/hub.html b/src/zenserver/frontend/html/compute/hub.html index f66ba94d5..32e1b05db 100644 --- a/src/zenserver/frontend/html/compute/hub.html +++ b/src/zenserver/frontend/html/compute/hub.html @@ -3,157 +3,17 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <script src="banner.js" defer></script> - <script src="nav.js" defer></script> + <link rel="stylesheet" type="text/css" href="../zen.css" /> + <script src="../theme.js"></script> + <script src="../banner.js" defer></script> + <script src="../nav.js" defer></script> <title>Zen Hub Dashboard</title> - <style> - * { - margin: 0; - padding: 0; - box-sizing: border-box; - } - - body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - background: #0d1117; - color: #c9d1d9; - padding: 20px; - } - - .container { - max-width: 1400px; - margin: 0 auto; - } - - .timestamp { - font-size: 12px; - color: #6e7681; - } - - .grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 20px; - margin-bottom: 30px; - } - - .card { - background: #161b22; - border: 1px solid #30363d; - border-radius: 6px; - padding: 20px; - } - - .card-title { - font-size: 14px; - font-weight: 600; - color: #8b949e; - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; - } - - .metric-value { - font-size: 36px; - font-weight: 600; - color: #f0f6fc; - line-height: 1; - } - - .metric-label { - font-size: 12px; - color: #8b949e; - margin-top: 4px; - } - - .progress-bar { - width: 100%; - height: 8px; - background: #21262d; - border-radius: 4px; - overflow: hidden; - margin-top: 8px; - } - - .progress-fill { - height: 100%; - background: #58a6ff; - transition: width 0.3s ease; - } - - .error { - color: #f85149; - padding: 12px; - background: #1c1c1c; - border-radius: 6px; - margin: 20px 0; - font-size: 13px; - } - - .section-title { - font-size: 20px; - font-weight: 600; - margin-bottom: 20px; - color: #f0f6fc; - } - - table { - width: 100%; - border-collapse: collapse; - font-size: 13px; - } - - th { - text-align: left; - color: #8b949e; - padding: 8px 12px; - border-bottom: 1px solid #30363d; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - font-size: 11px; - } - - td { - padding: 8px 12px; - border-bottom: 1px solid #21262d; - color: #c9d1d9; - } - - tr:last-child td { - border-bottom: none; - } - - .status-badge { - display: inline-block; - padding: 2px 8px; - border-radius: 4px; - font-size: 11px; - font-weight: 600; - } - - .status-badge.active { - background: rgba(63, 185, 80, 0.15); - color: #3fb950; - } - - .status-badge.inactive { - background: rgba(139, 148, 158, 0.15); - color: #8b949e; - } - - .empty-state { - color: #6e7681; - font-size: 13px; - padding: 20px 0; - text-align: center; - } - </style> </head> <body> - <div class="container"> - <zen-banner cluster-status="nominal" subtitle="HUB" tagline="Overview"></zen-banner> + <div class="container" style="max-width: 1400px; margin: 0 auto;"> + <zen-banner cluster-status="nominal" subtitle="HUB" tagline="Overview" logo-src="../favicon.ico"></zen-banner> <zen-nav> + <a href="/dashboard/">Home</a> <a href="hub.html">Hub</a> </zen-nav> <div class="timestamp">Last updated: <span id="last-update">Never</span></div> diff --git a/src/zenserver/frontend/html/compute/nav.js b/src/zenserver/frontend/html/compute/nav.js deleted file mode 100644 index 8ec42abd0..000000000 --- a/src/zenserver/frontend/html/compute/nav.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * zen-nav.js — Zen dashboard navigation bar Web Component - * - * Usage: - * <script src="nav.js" defer></script> - * - * <zen-nav> - * <a href="compute.html">Node</a> - * <a href="orchestrator.html">Orchestrator</a> - * </zen-nav> - * - * Each child <a> becomes a nav link. The current page is - * highlighted automatically based on the href. - */ - -class ZenNav extends HTMLElement { - - connectedCallback() { - if (!this.shadowRoot) this.attachShadow({ mode: 'open' }); - this._render(); - } - - _render() { - const currentPath = window.location.pathname; - const items = Array.from(this.querySelectorAll(':scope > a')); - - const links = items.map(a => { - const href = a.getAttribute('href') || ''; - const label = a.textContent.trim(); - const active = currentPath.endsWith(href); - return `<a class="nav-link${active ? ' active' : ''}" href="${href}">${label}</a>`; - }).join(''); - - this.shadowRoot.innerHTML = ` - <style> - *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - - :host { - display: block; - margin-bottom: 16px; - } - - .nav-bar { - display: flex; - align-items: center; - gap: 4px; - padding: 4px; - background: #161b22; - border: 1px solid #30363d; - border-radius: 6px; - } - - .nav-link { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - font-size: 13px; - font-weight: 500; - color: #8b949e; - text-decoration: none; - padding: 6px 14px; - border-radius: 4px; - transition: color 0.15s, background 0.15s; - } - - .nav-link:hover { - color: #c9d1d9; - background: #21262d; - } - - .nav-link.active { - color: #f0f6fc; - background: #30363d; - } - </style> - <nav class="nav-bar">${links}</nav> - `; - } -} - -customElements.define('zen-nav', ZenNav); diff --git a/src/zenserver/frontend/html/compute/orchestrator.html b/src/zenserver/frontend/html/compute/orchestrator.html index 2ee57b6b3..a519dee18 100644 --- a/src/zenserver/frontend/html/compute/orchestrator.html +++ b/src/zenserver/frontend/html/compute/orchestrator.html @@ -3,47 +3,12 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <script src="banner.js" defer></script> - <script src="nav.js" defer></script> + <link rel="stylesheet" type="text/css" href="../zen.css" /> + <script src="../theme.js"></script> + <script src="../banner.js" defer></script> + <script src="../nav.js" defer></script> <title>Zen Orchestrator Dashboard</title> <style> - * { - margin: 0; - padding: 0; - box-sizing: border-box; - } - - body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - background: #0d1117; - color: #c9d1d9; - padding: 20px; - } - - .container { - max-width: 1400px; - margin: 0 auto; - } - - h1 { - font-size: 32px; - font-weight: 600; - margin-bottom: 10px; - color: #f0f6fc; - } - - .header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 30px; - } - - .timestamp { - font-size: 12px; - color: #6e7681; - } - .agent-count { display: flex; align-items: center; @@ -51,144 +16,22 @@ font-size: 14px; padding: 8px 16px; border-radius: 6px; - background: #161b22; - border: 1px solid #30363d; + background: var(--theme_g3); + border: 1px solid var(--theme_g2); } .agent-count .count { font-size: 20px; font-weight: 600; - color: #f0f6fc; - } - - .card { - background: #161b22; - border: 1px solid #30363d; - border-radius: 6px; - padding: 20px; - } - - .card-title { - font-size: 14px; - font-weight: 600; - color: #8b949e; - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; - } - - .error { - color: #f85149; - padding: 12px; - background: #1c1c1c; - border-radius: 6px; - margin: 20px 0; - font-size: 13px; - } - - table { - width: 100%; - border-collapse: collapse; - font-size: 13px; - } - - th { - text-align: left; - color: #8b949e; - padding: 8px 12px; - border-bottom: 1px solid #30363d; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - font-size: 11px; - } - - td { - padding: 8px 12px; - border-bottom: 1px solid #21262d; - color: #c9d1d9; - } - - tr:last-child td { - border-bottom: none; - } - - .total-row td { - border-top: 2px solid #30363d; - font-weight: 600; - color: #f0f6fc; - } - - .health-dot { - display: inline-block; - width: 10px; - height: 10px; - border-radius: 50%; - } - - .health-green { - background: #3fb950; - } - - .health-yellow { - background: #d29922; - } - - .health-red { - background: #f85149; - } - - a { - color: #58a6ff; - text-decoration: none; - } - - a:hover { - text-decoration: underline; - } - - .empty-state { - color: #6e7681; - font-size: 13px; - padding: 20px 0; - text-align: center; - } - - .history-tabs { - display: flex; - gap: 4px; - background: #0d1117; - border-radius: 6px; - padding: 2px; - } - - .history-tab { - background: transparent; - border: none; - color: #8b949e; - font-size: 12px; - font-weight: 600; - padding: 4px 12px; - border-radius: 4px; - cursor: pointer; - text-transform: uppercase; - letter-spacing: 0.5px; - } - - .history-tab:hover { - color: #c9d1d9; - } - - .history-tab.active { - background: #30363d; - color: #f0f6fc; + color: var(--theme_bright); } </style> </head> <body> - <div class="container"> - <zen-banner cluster-status="nominal" load="0"></zen-banner> + <div class="container" style="max-width: 1400px; margin: 0 auto;"> + <zen-banner cluster-status="nominal" load="0" logo-src="../favicon.ico"></zen-banner> <zen-nav> + <a href="/dashboard/">Home</a> <a href="compute.html">Node</a> <a href="orchestrator.html">Orchestrator</a> </zen-nav> @@ -513,8 +356,8 @@ '<td style="text-align: right;">' + actionsPending + '</td>' + '<td style="text-align: right;">' + actionsRunning + '</td>' + '<td style="text-align: right;">' + actionsCompleted + '</td>' + - '<td style="text-align: right; color: #8b949e; font-size: 11px;">' + formatTraffic(bytesRecv, bytesSent) + '</td>' + - '<td style="text-align: right; color: #8b949e;">' + formatLastSeen(dt) + '</td>'; + '<td style="text-align: right; font-size: 11px; color: var(--theme_g1);">' + formatTraffic(bytesRecv, bytesSent) + '</td>' + + '<td style="text-align: right; color: var(--theme_g1);">' + formatLastSeen(dt) + '</td>'; tbody.appendChild(tr); } @@ -527,7 +370,7 @@ totalTr.className = 'total-row'; totalTr.innerHTML = '<td></td>' + - '<td style="text-align: right; color: #8b949e; text-transform: uppercase; font-size: 11px;">Total' + (cidr ? ' <span style="font-family: monospace; font-weight: normal;">' + escapeHtml(cidr) + '</span>' : '') + '</td>' + + '<td style="text-align: right; color: var(--theme_g1); text-transform: uppercase; font-size: 11px;">Total' + (cidr ? ' <span style="font-family: monospace; font-weight: normal;">' + escapeHtml(cidr) + '</span>' : '') + '</td>' + '<td style="text-align: right;">' + totalCpus + '</td>' + '<td></td>' + '<td style="text-align: right;">' + formatMemory(totalMemUsed, totalMemTotal) + '</td>' + @@ -560,11 +403,11 @@ } function eventBadge(type) { - var colors = { joined: '#3fb950', left: '#f85149', returned: '#d29922' }; + var colors = { joined: 'var(--theme_ok)', left: 'var(--theme_fail)', returned: 'var(--theme_warn)' }; var labels = { joined: 'Joined', left: 'Left', returned: 'Returned' }; - var color = colors[type] || '#8b949e'; + var color = colors[type] || 'var(--theme_g1)'; var label = labels[type] || type; - return '<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;color:#0d1117;background:' + color + ';">' + escapeHtml(label) + '</span>'; + return '<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;color:var(--theme_g4);background:' + color + ';">' + escapeHtml(label) + '</span>'; } function formatTimestamp(ts) { @@ -613,7 +456,7 @@ var evt = events[i]; var tr = document.createElement('tr'); tr.innerHTML = - '<td style="color: #8b949e;">' + formatTimestamp(evt.ts) + '</td>' + + '<td style="color: var(--theme_g1);">' + formatTimestamp(evt.ts) + '</td>' + '<td>' + eventBadge(evt.type) + '</td>' + '<td>' + escapeHtml(evt.worker_id || '') + '</td>' + '<td>' + escapeHtml(evt.hostname || '') + '</td>'; @@ -652,7 +495,7 @@ var sessionBadge = ''; if (c.session_id) { - sessionBadge = ' <span style="font-family:monospace;font-size:10px;color:#6e7681;" title="Session ' + escapeHtml(c.session_id) + '">' + escapeHtml(c.session_id.substring(0, 8)) + '</span>'; + sessionBadge = ' <span style="font-family:monospace;font-size:10px;color:var(--theme_faint);" title="Session ' + escapeHtml(c.session_id) + '">' + escapeHtml(c.session_id.substring(0, 8)) + '</span>'; } var tr = document.createElement('tr'); @@ -660,18 +503,18 @@ '<td style="text-align: center;"><span class="health-dot ' + hClass + '" title="' + escapeHtml(hTitle) + '"></span></td>' + '<td>' + escapeHtml(c.id || '') + sessionBadge + '</td>' + '<td>' + escapeHtml(c.hostname || '') + '</td>' + - '<td style="font-family: monospace; font-size: 12px; color: #8b949e;">' + escapeHtml(c.address || '') + '</td>' + - '<td style="text-align: right; color: #8b949e;">' + formatLastSeen(dt) + '</td>'; + '<td style="font-family: monospace; font-size: 12px; color: var(--theme_g1);">' + escapeHtml(c.address || '') + '</td>' + + '<td style="text-align: right; color: var(--theme_g1);">' + formatLastSeen(dt) + '</td>'; tbody.appendChild(tr); } } function clientEventBadge(type) { - var colors = { connected: '#3fb950', disconnected: '#f85149', updated: '#d29922' }; + var colors = { connected: 'var(--theme_ok)', disconnected: 'var(--theme_fail)', updated: 'var(--theme_warn)' }; var labels = { connected: 'Connected', disconnected: 'Disconnected', updated: 'Updated' }; - var color = colors[type] || '#8b949e'; + var color = colors[type] || 'var(--theme_g1)'; var label = labels[type] || type; - return '<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;color:#0d1117;background:' + color + ';">' + escapeHtml(label) + '</span>'; + return '<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;color:var(--theme_g4);background:' + color + ';">' + escapeHtml(label) + '</span>'; } function renderClientHistory(events) { @@ -693,7 +536,7 @@ var evt = events[i]; var tr = document.createElement('tr'); tr.innerHTML = - '<td style="color: #8b949e;">' + formatTimestamp(evt.ts) + '</td>' + + '<td style="color: var(--theme_g1);">' + formatTimestamp(evt.ts) + '</td>' + '<td>' + clientEventBadge(evt.type) + '</td>' + '<td>' + escapeHtml(evt.client_id || '') + '</td>' + '<td>' + escapeHtml(evt.hostname || '') + '</td>'; |