aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/frontend/html/zen.css
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-23 14:19:57 +0100
committerGitHub Enterprise <[email protected]>2026-03-23 14:19:57 +0100
commit2a445406e09328cb4cf320300f2678997d6775b7 (patch)
treea92f02d94c92144cb6ae32160397298533e4c822 /src/zenserver/frontend/html/zen.css
parentadd hub instance crash recovery (#885) (diff)
downloadzen-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.css450
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 {