aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/frontend/html/compute
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-09 17:43:08 +0100
committerGitHub Enterprise <[email protected]>2026-03-09 17:43:08 +0100
commitb37b34ea6ad906f54e8104526e77ba66aed997da (patch)
treee80ce17d666aff6d2f0d73d4977128ffb4055476 /src/zenserver/frontend/html/compute
parentadd fallback for zencache multirange (#816) (diff)
downloadzen-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.js321
-rw-r--r--src/zenserver/frontend/html/compute/compute.html327
-rw-r--r--src/zenserver/frontend/html/compute/hub.html154
-rw-r--r--src/zenserver/frontend/html/compute/nav.js79
-rw-r--r--src/zenserver/frontend/html/compute/orchestrator.html205
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>';