diff options
| author | Stefan Boberg <[email protected]> | 2026-03-23 14:19:57 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-23 14:19:57 +0100 |
| commit | 2a445406e09328cb4cf320300f2678997d6775b7 (patch) | |
| tree | a92f02d94c92144cb6ae32160397298533e4c822 /src/zenserver/frontend/html/zen.css | |
| parent | add hub instance crash recovery (#885) (diff) | |
| download | zen-2a445406e09328cb4cf320300f2678997d6775b7.tar.xz zen-2a445406e09328cb4cf320300f2678997d6775b7.zip | |
Dashboard refresh (logs, storage, network, object store, docs) (#835)
## Summary
This PR adds a session management service, several new dashboard pages, and a number of infrastructure improvements.
### Sessions Service
- `SessionsServiceClient` in `zenutil` announces sessions to a remote zenserver with a 15s heartbeat (POST/PUT/DELETE lifecycle)
- Storage server registers itself with its own local sessions service on startup
- Session mode attribute coupled to server mode (Compute, Proxy, Hub, etc.)
- Ended sessions tracked with `ended_at` timestamp; status filtering (Active/Ended/All)
- `--sessions-url` config option for remote session announcement
- In-process log sink (`InProcSessionLogSink`) forwards server log output to the server's own session, visible in the dashboard
### Session Log Viewer
- POST/GET endpoints for session logs (`/sessions/{id}/log`) supporting raw text and structured JSON/CbObject with batch `entries` array
- In-memory log storage per session (capped at 10k entries) with cursor-based pagination for efficient incremental fetching
- Log panel in the sessions dashboard with incremental DOM updates, auto-scroll (Follow toggle), newest-first toggle, text filter, and log-level coloring
- Auto-selects the server's own session on page load
### TCP Log Streaming
- `LogStreamListener` and `TcpLogStreamSink` for log delivery over TCP
- Sequence numbers on each message with drop detection and synthetic "dropped" notice on gaps
- Gathered buffer writes to reduce syscall overhead when flushing batches
- Tests covering basic delivery, multi-line splitting, drop detection, and sequencing
### New Dashboard Pages
- **Sessions**: master-detail layout with selectable rows, metadata panel, live WebSocket updates, paging, abbreviated date formatting, and "this" pill for the local session
- **Object Store**: summary stats tiles and bucket table with click-to-expand inline object listing (`GET /obj/`)
- **Storage**: per-volume disk usage breakdown (`GET /admin/storage`), Garbage Collection status section (next-run countdown, last-run stats), and GC History table with paginated rows and expandable detail panels
- **Network**: overview tiles, per-service request table, proxy connections, and live WebSocket updates; distinct client IPs and session counts via HyperLogLog
### Documentation Page
- In-dashboard Docs page with sidebar navigation, markdown rendering (via `marked`), Mermaid diagram support (theme-aware), collapsible sections, text filtering with highlighting, and cross-document linking
- New user-facing docs: `overview.md` (with architecture and per-mode diagrams), `sessions.md`, `cache.md`, `projects.md`; updated `compute.md`
- Dev docs moved to `docs/dev/`
### Infrastructure & Bug Fixes
- **Deflate compression** for the embedded frontend zip (~3.4MB → ~950KB); zlib inflate support added to `ZipFs` with cached decompressed buffers
- **Local IP addresses**: `GetLocalIpAddresses()` (Windows via `GetAdaptersAddresses`, Linux/Mac via `getifaddrs`); surfaced in `/status/status`, `/health/info`, and the dashboard banner
- **Dashboard nav**: unified into `zen-nav` web component with `MutationObserver` for dynamically added links, CSS `::part()` to merge banner/nav border radii, and prefix-based active link detection
- Stats broadcast refactored from manual JSON string concatenation to `CbObjectWriter`; `CbObject`-to-JS conversion improved for `TimeSpan`, `DateTime`, and large integers
- Stats WebSocket boilerplate consolidated into `ZenPage.connect_stats_ws()`
Diffstat (limited to 'src/zenserver/frontend/html/zen.css')
| -rw-r--r-- | src/zenserver/frontend/html/zen.css | 450 |
1 files changed, 421 insertions, 29 deletions
diff --git a/src/zenserver/frontend/html/zen.css b/src/zenserver/frontend/html/zen.css index 49a1e6d21..b4f7270fc 100644 --- a/src/zenserver/frontend/html/zen.css +++ b/src/zenserver/frontend/html/zen.css @@ -27,6 +27,7 @@ --theme_bright: #1f2328; --theme_faint: #6e7781; --theme_border_subtle: #d8dee4; + --theme_highlight: #b8860b44; } } @@ -54,6 +55,7 @@ --theme_bright: #f0f6fc; --theme_faint: #6e7681; --theme_border_subtle: #21262d; + --theme_highlight: #e3b341aa; } } @@ -81,6 +83,7 @@ --theme_bright: #1f2328; --theme_faint: #6e7781; --theme_border_subtle: #d8dee4; + --theme_highlight: #b8860b44; } :root[data-theme="dark"] { @@ -106,6 +109,7 @@ --theme_bright: #f0f6fc; --theme_faint: #6e7681; --theme_border_subtle: #21262d; + --theme_highlight: #e3b341aa; } /* theme toggle ------------------------------------------------------------- */ @@ -214,34 +218,6 @@ button { } } -/* service nav -------------------------------------------------------------- */ - -#service_nav { - display: flex; - align-items: center; - gap: 4px; - margin-bottom: 16px; - padding: 4px; - background-color: var(--theme_g3); - border: 1px solid var(--theme_g2); - border-radius: 6px; - - a { - padding: 6px 14px; - border-radius: 4px; - font-size: 13px; - font-weight: 500; - color: var(--theme_g1); - text-decoration: none; - transition: color 0.15s, background 0.15s; - } - - a:hover { - background-color: var(--theme_p4); - color: var(--theme_g0); - text-decoration: none; - } -} /* links -------------------------------------------------------------------- */ @@ -358,6 +334,207 @@ a { } } +/* sessions ----------------------------------------------------------------- */ + +.sessions-section { + display: flex; + flex-direction: column; + height: calc(100vh - 80px); +} + +.sessions-layout { + display: flex; + gap: 1.5em; + align-items: flex-start; + flex-shrink: 0; +} + +.sessions-table { + flex: 1; + min-width: 0; +} + +.sessions-table .zen_table > div > div { + text-align: right; +} + +.sessions-table .zen_table > div > div:nth-child(2) { + font-family: 'SF Mono', 'Cascadia Mono', Consolas, 'DejaVu Sans Mono', monospace; +} + +.sessions-detail { + width: 600px; + flex-shrink: 0; + font-size: 13px; +} + +.sessions-detail h3 { + margin: 0 0 0.6em 0; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--theme_g1); +} + +.sessions-detail .zen_table { + margin-bottom: 1em; +} + +.sessions-detail-placeholder { + color: var(--theme_g1); + font-style: italic; +} + +.sessions-selected { + background-color: var(--theme_p3) !important; +} + +.sessions-log-panel { + margin-top: 12px; + border: 1px solid var(--theme_g3); + border-radius: 6px; + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + min-height: 200px; +} +.sessions-log-header { + display: flex; + align-items: center; + padding: 6px 12px; + background-color: var(--theme_bg1); + border-bottom: 1px solid var(--theme_g3); + flex-shrink: 0; +} +.sessions-log-title { + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--theme_g1); +} +.sessions-log-filter { + margin-left: 12px; + padding: 6px 12px; + font-size: 14px; + font-family: inherit; + border: 1px solid var(--theme_g2); + border-radius: 6px; + background: var(--theme_bg1); + color: var(--theme_bright); + outline: none; + width: 200px; +} +.sessions-log-filter:focus { + border-color: var(--theme_ln); + background: var(--theme_bg0); +} +.sessions-log-filter::placeholder { + color: var(--theme_g1); +} +.sessions-log-body { + flex: 1; + min-height: 0; + overflow-y: auto; + font-family: 'SF Mono', 'Cascadia Mono', Consolas, 'DejaVu Sans Mono', monospace; + font-size: 12px; + line-height: 1.5; + padding: 4px 0; + background-color: var(--theme_bg0); +} +.sessions-log-empty { + color: var(--theme_g1); + font-style: italic; + padding: 8px 12px; + font-family: inherit; +} +.sessions-log-trimmed { + color: var(--theme_g1); + font-style: italic; + padding: 2px 12px; + font-size: 11px; +} +.sessions-log-line { + padding: 0 12px; + white-space: pre; + display: flex; + gap: 8px; +} +.sessions-log-line:hover { + background-color: var(--theme_bg1); +} +.sessions-log-ts { + color: var(--theme_g1); + flex-shrink: 0; +} +.sessions-log-level { + flex-shrink: 0; + min-width: 4em; + font-weight: 600; +} +.sessions-log-level-info { color: var(--theme_ln); } +.sessions-log-level-warn { color: #d29922; } +.sessions-log-level-error { color: #f85149; } +.sessions-log-level-debug { color: var(--theme_g1); } +.sessions-log-msg { + white-space: pre-wrap; + word-break: break-all; +} +.sessions-log-data { + color: var(--theme_g1); + white-space: pre-wrap; + word-break: break-all; +} + +.sessions-self-pill { + display: inline-block; + font-size: 0.7em; + font-weight: 600; + padding: 1px 6px; + margin-right: 6px; + border-radius: 8px; + background-color: var(--theme_p4); + color: var(--theme_g0); + vertical-align: middle; +} + +.objectstore-bucket-detail { + grid-column: 1 / -1; + padding: 8px 16px; + background-color: var(--theme_bg1); + border-top: 1px solid var(--theme_g3); + font-size: 0.9em; +} +.objectstore-objects-table { + width: 100%; + border-collapse: collapse; +} +.objectstore-objects-table th { + text-align: left; + font-weight: 600; + padding: 4px 8px; + border-bottom: 1px solid var(--theme_g3); + color: var(--theme_g1); + font-size: 0.85em; +} +.objectstore-objects-table td { + padding: 2px 8px; + font-family: var(--font_mono); + font-size: 0.85em; +} + +.sessions-pager { + display: flex; + align-items: center; + gap: 8px; + margin-top: 8px; +} +.sessions-pager-label { + font-size: 0.85em; + opacity: 0.7; +} + /* expandable cell ---------------------------------------------------------- */ .zen_expand_icon { @@ -560,6 +737,20 @@ zen-banner { margin-bottom: 24px; } +zen-banner:has(+ zen-nav) { + margin-bottom: -1px; +} + +zen-banner:has(+ zen-nav)::part(banner) { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +zen-banner + zen-nav::part(nav-bar) { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + /* error -------------------------------------------------------------------- */ #error { @@ -695,7 +886,7 @@ zen-banner { /* info --------------------------------------------------------------------- */ -#info { +#info, #storage, #network { .info-tiles { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); } @@ -1084,6 +1275,207 @@ tr:last-child td { color: var(--theme_bright); } +/* docs --------------------------------------------------------------------- */ + +.docs-filter { + display: block; + width: 100%; + max-width: 300px; + padding: 8px 12px; + font-size: 14px; + font-family: inherit; + border: 1px solid var(--theme_g2); + border-radius: 6px; + background: var(--theme_bg1); + color: var(--theme_bright); + outline: none; + margin-bottom: 12px; +} +.docs-filter:focus { + border-color: var(--theme_ln); + background: var(--theme_bg0); +} +.docs-filter::placeholder { + color: var(--theme_g1); +} +.docs-highlight { + background: var(--theme_highlight); + color: inherit; + border-radius: 2px; + padding: 0 1px; +} +.docs-layout { + display: flex; + gap: 24px; +} +.docs-sidebar { + flex-shrink: 0; + width: 200px; + border-right: 1px solid var(--theme_g3); + padding-right: 16px; + display: flex; + flex-direction: column; + gap: 2px; + position: sticky; + top: 16px; + align-self: flex-start; +} +.docs-sidebar-link { + display: block; + padding: 6px 10px; + font-size: 13px; + color: var(--theme_g1); + text-decoration: none; + border-radius: 4px; +} +.docs-sidebar-link:hover { + color: var(--theme_g0); + background: var(--theme_g4); +} +.docs-sidebar-link.active { + color: var(--theme_bright); + background: var(--theme_g3); + font-weight: 500; +} +.docs-content { + flex: 1; + min-width: 0; + font-size: 16px; + line-height: 1.5; + color: var(--theme_g0); +} +.docs-content h1 { + font-size: 24px; + font-weight: 600; + color: var(--theme_bright); + margin: 0 0 16px 0; + padding-bottom: 8px; + border-bottom: 1px solid var(--theme_g3); + display: flex; + align-items: center; + gap: 8px; +} +.docs-source-link { + font-size: 14px; + opacity: 0.4; + transition: opacity 0.15s; + text-decoration: none; +} +.docs-source-link::after { + content: "\2197"; +} +.docs-source-link:hover, +.docs-github-link:hover { + opacity: 1; +} +.docs-github-link { + font-size: 14px; + opacity: 0.4; + transition: opacity 0.15s; + text-decoration: none; +} +.docs-github-link::after { + content: "GH"; + font-size: 10px; + font-weight: 700; + letter-spacing: -0.5px; +} +.docs-section { + margin-top: 16px; +} +.docs-section-title { + font-size: 18px; + font-weight: 600; + color: var(--theme_bright); + cursor: pointer; + list-style: none; + user-select: none; + padding-bottom: 6px; + border-bottom: 1px solid var(--theme_g4); + margin-bottom: 8px; +} +.docs-section-title::-webkit-details-marker { + display: none; +} +.docs-section-title::before { + content: "\25B6"; + display: inline-block; + margin-right: 8px; + font-size: 10px; + transition: transform 0.15s; + color: var(--theme_g1); +} +.docs-section[open] > .docs-section-title::before { + transform: rotate(90deg); +} +.docs-content h2 { + font-size: 18px; + font-weight: 600; + color: var(--theme_bright); + margin: 24px 0 8px 0; +} +.docs-content h3 { + font-size: 15px; + font-weight: 600; + color: var(--theme_bright); + margin: 16px 0 6px 0; +} +.docs-content p { + margin: 0 0 12px 0; +} +.docs-content code { + background: var(--theme_bg1); + padding: 1px 5px; + border-radius: 3px; + font-family: 'SF Mono', 'Cascadia Mono', Consolas, 'DejaVu Sans Mono', monospace; + font-size: 0.9em; + color: var(--theme_bright); +} +.docs-content pre { + background: var(--theme_bg1); + padding: 12px 16px; + border-radius: 6px; + overflow-x: auto; + margin: 0 0 12px 0; +} +.docs-content pre code { + background: none; + padding: 0; +} +.docs-content ul, .docs-content ol { + margin: 4px 0 12px 0; + padding-left: 24px; +} +.docs-content table { + border-collapse: collapse; + margin: 8px 0 12px 0; + font-size: 13px; +} +.docs-content th, .docs-content td { + border: 1px solid var(--theme_g3); + padding: 4px 10px; +} +.docs-content th { + background: var(--theme_bg1); + font-weight: 600; + color: var(--theme_bright); +} +.docs-content a { + color: var(--theme_ln); +} +.docs-mermaid { + margin: 12px 0; + overflow-x: auto; +} +.docs-mermaid svg { + max-width: 100%; + height: auto; +} +.docs-loading, .docs-error { + color: var(--theme_g1); + font-style: italic; +} + /* module action controls --------------------------------------------------- */ .module-state-dot { |