From 5d9f161706306ab857b0bb06257b799a14a92789 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Fri, 10 Jul 2026 09:37:58 -0400 Subject: [PATCH] feat: extend the record-row design system to Host Overview, Alerts, and About Extract the settings redesign's tokens and components into shared static/hbd-ui.css + hbd-ui.js and dedupe settings.html against them. About becomes yaml-key sections with kv rows; Alerts gets stat tiles, chip filters, and alert record rows (same fetch/ack logic); Host Overview keeps its DOM and live-update JS but is re-skinned to the token system with a page toolbar. Live Dashboard intentionally untouched pending its own redesign. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NfPpSpccTWBfZg1FTveyaU --- hbd/server/static/hbd-ui.css | 155 +++++++ hbd/server/static/hbd-ui.js | 16 + hbd/server/templates/about.html | 249 ++++------- hbd/server/templates/alerts.html | 635 +++++++---------------------- hbd/server/templates/plugins.html | 476 +++++++-------------- hbd/server/templates/settings.html | 147 +------ 6 files changed, 551 insertions(+), 1127 deletions(-) create mode 100644 hbd/server/static/hbd-ui.css create mode 100644 hbd/server/static/hbd-ui.js diff --git a/hbd/server/static/hbd-ui.css b/hbd/server/static/hbd-ui.css new file mode 100644 index 0000000..44f4265 --- /dev/null +++ b/hbd/server/static/hbd-ui.css @@ -0,0 +1,155 @@ +/* hbd-ui.css — shared design system for hbd pages. + * + * Green-biased neutrals, teal accent, mono identifiers. Pages link this + * after static/style.css and build on the tokens + components below. + * Introduced with the settings-page redesign; see settings.html for the + * fullest use of the row/editor idiom. + */ + +:root { + --st-paper: #f4f6f4; + --st-surface: #ffffff; + --st-surface-2: #eef1ee; + --st-ink: #1d2420; + --st-muted: #5f6a63; + --st-faint: #8a948d; + --st-line: #dde3de; + --st-line-soft: #e8ece8; + --st-accent: #0e7280; + --st-accent-ink: #ffffff; + --st-accent-soft: #e0eef0; + --st-ok: #3f9c5a; + --st-ok-soft: #e2f2e7; + --st-warn: #d9982a; + --st-warn-soft: #f9efdc; + --st-crit: #d64545; + --st-crit-soft: #f9e3e3; + --st-badge-owner: #ede7f6; --st-badge-owner-ink: #5e35b1; + --st-badge-mgr: #e3f2fd; --st-badge-mgr-ink: #1565c0; + --st-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace; +} +html[data-theme="dark"] { + --st-paper: #131816; + --st-surface: #1b211e; + --st-surface-2: #222925; + --st-ink: #e4eae6; + --st-muted: #9aa69e; + --st-faint: #6e7a72; + --st-line: #2c342f; + --st-line-soft: #252c28; + --st-accent: #4cc4d4; + --st-accent-ink: #0d2326; + --st-accent-soft: #143b40; + --st-ok: #5cba77; + --st-ok-soft: #1c2f22; + --st-warn: #e0ad4e; + --st-warn-soft: #33290f; + --st-crit: #e06c6c; + --st-crit-soft: #3a1a1a; + --st-badge-owner: #33294a; --st-badge-owner-ink: #b39ddb; + --st-badge-mgr: #1b3350; --st-badge-mgr-ink: #90caf9; +} + +/* ── sticky toolbar under the site nav ── */ +.st-toolbar { + position: sticky; top: var(--nav-h, 48px); z-index: 90; + display: flex; align-items: center; gap: 12px; flex-wrap: wrap; + padding: 9px 20px; + background: var(--st-surface); border-bottom: 1px solid var(--st-line); +} +.st-toolbar .brand { font-family: var(--st-mono); font-size: 15px; font-weight: 700; letter-spacing: -.02em; color: var(--st-ink); } +.st-toolbar .brand .tld { color: var(--st-accent); } +.st-toolbar .hintline { font-size: 12px; color: var(--st-faint); } +.st-toolbar .spacer { flex: 1; } + +/* ── ECG pulse divider ── */ +.pulse { display: block; width: 100%; height: 14px; } +.pulse polyline { fill: none; stroke: var(--st-accent); stroke-width: 1.5; opacity: .55; } + +/* ── buttons ── */ +.btn { + font: 600 13px/1 inherit; border: 1px solid var(--st-line); border-radius: 6px; + background: var(--st-surface); color: var(--st-ink); padding: 7px 14px; cursor: pointer; +} +.btn:focus-visible, a:focus-visible { outline: 2px solid var(--st-accent); outline-offset: 2px; } +.btn.primary { background: var(--st-accent); border-color: var(--st-accent); color: var(--st-accent-ink); } +.btn.quiet { border-color: transparent; background: transparent; color: var(--st-muted); } +.btn.small { padding: 4px 10px; font-size: 12px; } +.btn.danger { color: var(--st-crit); } +.btn:disabled { opacity: .6; cursor: default; } + +/* ── section headers as config keys ── */ +section.st { margin-top: 26px; scroll-margin-top: calc(var(--bars-h, 94px) + 12px); } +.sec-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; } +.sec-head h2 { margin: 0; font-family: var(--st-mono); font-size: 15px; font-weight: 700; color: var(--st-ink); } +.sec-head h2 .colon { color: var(--st-accent); } +.sec-head .count { font-family: var(--st-mono); font-size: 11.5px; color: var(--st-faint); } +.sec-head .sub { font-size: 12px; color: var(--st-muted); } +.sec-head .add { margin-left: auto; } + +/* ── record list + row ── */ +.list { background: var(--st-surface); border: 1px solid var(--st-line); border-radius: 8px; overflow: hidden; } +.row { + display: grid; grid-template-columns: 190px minmax(0,1fr) auto; + gap: 4px 14px; align-items: center; + padding: 8px 14px; border-bottom: 1px solid var(--st-line-soft); + transition: background .12s ease; +} +@media (prefers-reduced-motion: reduce) { .row { transition: none; } } +.row:last-child { border-bottom: none; } +.row:hover { background: var(--st-surface-2); } +.row .id { font-family: var(--st-mono); font-size: 13px; font-weight: 600; color: var(--st-ink); display: flex; align-items: center; gap: 8px; min-width: 0; } +.row .id .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.row .act { display: flex; gap: 4px; justify-content: flex-end; } + +.dot { width: 8px; height: 8px; border-radius: 50%; flex: none; } +.dot.ok { background: var(--st-ok); } +.dot.warn { background: var(--st-warn); } +.dot.crit { background: var(--st-crit); } +.dot.off { background: transparent; border: 1.5px solid var(--st-faint); } + +.facts { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: center; min-width: 0; } +.chip { + display: inline-flex; align-items: center; gap: 4px; + font-family: var(--st-mono); font-size: 11px; line-height: 1.3; + padding: 3px 7px; border-radius: 4px; + background: var(--st-surface-2); color: var(--st-muted); +} +.chip.owner { background: var(--st-badge-owner); color: var(--st-badge-owner-ink); } +.chip.mgr { background: var(--st-badge-mgr); color: var(--st-badge-mgr-ink); } +.chip.level { background: transparent; border: 1px solid var(--st-line); } +.chip.private { background: transparent; border: 1px dashed var(--st-badge-owner-ink); color: var(--st-badge-owner-ink); } +.chip.ok { background: var(--st-ok-soft); color: var(--st-ok); } +.chip.warn { background: var(--st-warn-soft); color: var(--st-warn); } +.chip.crit { background: var(--st-crit-soft); color: var(--st-crit); } +.chip .k { color: var(--st-faint); } +.chip .w { color: var(--st-warn); font-weight: 700; } +.chip .c { color: var(--st-crit); font-weight: 700; } + +/* ── key/value rows (server groups, about page) ── */ +.kv .row { grid-template-columns: 230px minmax(0,1fr); padding: 6px 14px; } +.group { + display: flex; align-items: center; gap: 10px; + padding: 7px 14px 5px; font-family: var(--st-mono); font-size: 11px; color: var(--st-faint); + background: var(--st-surface); border-bottom: 1px solid var(--st-line-soft); +} +.group .gbtn { margin-left: auto; } +.kv .val { font-family: var(--st-mono); font-size: 12.5px; color: var(--st-muted); min-width: 0; } +.kv .val a { color: var(--st-accent); text-decoration: none; } +.kv .val a:hover { text-decoration: underline; } +.kv .desc { display: block; font-size: 11px; color: var(--st-faint); margin-top: 2px; } +.val-masked { color: var(--st-faint); letter-spacing: 2px; } + +.note { margin: 8px 2px 0; font-size: 12px; color: var(--st-faint); } + +/* ── handheld defaults for rows ── */ +@media (max-width: 760px) { + .st-toolbar { padding: 8px 10px; } + .st-toolbar .hintline { display: none; } + .row { grid-template-columns: minmax(0,1fr) auto; padding: 9px 12px; } + .row .id { grid-row: 1; grid-column: 1; } + .row .act { grid-row: 1; grid-column: 2; } + .row .facts { grid-column: 1 / -1; } + .kv .row { grid-template-columns: 1fr; } + section.st { scroll-margin-top: calc(var(--bars-h, 94px) + 50px); } +} diff --git a/hbd/server/static/hbd-ui.js b/hbd/server/static/hbd-ui.js new file mode 100644 index 0000000..3c742be --- /dev/null +++ b/hbd/server/static/hbd-ui.js @@ -0,0 +1,16 @@ +/* hbd-ui.js — shared helpers for pages using hbd-ui.css. + * + * Keeps sticky toolbars stuck just below the fixed site nav, whose height + * varies with viewport width and wrapping. + */ + +function _setStickyOffsets() { + const nav = document.querySelector('.nav'); + const tb = document.querySelector('.st-toolbar'); + const navH = nav ? nav.offsetHeight : 48; + const barsH = navH + (tb ? tb.offsetHeight : 46); + document.documentElement.style.setProperty('--nav-h', navH + 'px'); + document.documentElement.style.setProperty('--bars-h', barsH + 'px'); +} +window.addEventListener('resize', _setStickyOffsets); +document.addEventListener('DOMContentLoaded', _setStickyOffsets); diff --git a/hbd/server/templates/about.html b/hbd/server/templates/about.html index 19c6c39..944ec82 100644 --- a/hbd/server/templates/about.html +++ b/hbd/server/templates/about.html @@ -1,189 +1,110 @@ {% include 'head.html' %} + + {% include 'nav.html' %} -
-

{{ header }}

-

Heartbeat monitoring system

+
+ hbd·about + heartbeat monitoring system +
+ -
-
-
- -
Lightweight host monitoring over UDP
+
+
+ + v{{ hbd_version }} + Lightweight host monitoring over UDP +
+ +
+
+

version:

+
+
+
+ server + {{ hbd_version }} +
+
+ python + {{ python_version }} +
+
+ license + MIT
- v{{ hbd_version }}
-
+ -
-

Version

-
- Server version - {{ hbd_version }} +
+
+

runtime:

-
- Python - {{ python_version }} +
+
+ host + {{ server_hostname }} +
+
+ started + {{ start_time_str }} +
+
+ uptime + {{ uptime_str }} +
+
+ hosts_monitored + {{ host_count }} +
-
- License - MIT -
-
+
-
-

Runtime

-
- Host - {{ server_hostname }} +
+
+

contact:

-
- Started - {{ start_time_str }} +
+
+ author + Andreas Wrede +
+
+ email + aew.hbd@wrede.ca +
+
-
- Uptime - {{ uptime_str }} -
-
- Hosts monitored - {{ host_count }} -
-
- -
-

Contact & Source

-
- Author - Andreas Wrede -
-
- Email - aew.hbd@wrede.ca -
- -
- +
{% include 'nav.html' %} -
-

{{ header }}

-

Real-time monitoring alerts and threshold violations

+
+ hbd·alerts + threshold violations and reachability — refreshes every 15 s +
+ -
-
-
Critical
-
-
-
-
-
Warning
-
-
-
-
-
Total Hosts
-
-
-
+
+
+
critical
+
warning
+
hosts
-
- Show: - - - - +
+ + + + + updated never
-
-
Last updated: Never
-
-
Loading alerts...
+
+
+

alerts:

+
-
- Auto-refreshing every 15 seconds +
+
Loading alerts…
-
+
diff --git a/hbd/server/templates/plugins.html b/hbd/server/templates/plugins.html index 507711f..5503732 100644 --- a/hbd/server/templates/plugins.html +++ b/hbd/server/templates/plugins.html @@ -2,38 +2,30 @@ {% include 'head.html' %} + + + {% include 'nav.html' %} +
+ hbd·overview + per-host metrics — expand a host for plugin details +
+ +
-

{{ header }}

-

Per-host system metrics — expand a host to see plugin details

{% if not hosts %}
diff --git a/hbd/server/templates/settings.html b/hbd/server/templates/settings.html index 7f83747..ece9a87 100644 --- a/hbd/server/templates/settings.html +++ b/hbd/server/templates/settings.html @@ -1,82 +1,20 @@ {% include 'head.html' %} + + @@ -1444,20 +1314,7 @@ }, { threshold: 0.2 }); _navSections.forEach(s => _navObserver.observe(s)); - // Keep the toolbar stuck just below the fixed site nav (whose height - // varies with viewport width and wrapping). - function _setStickyOffsets() { - const nav = document.querySelector('.nav'); - const tb = document.querySelector('.st-toolbar'); - const navH = nav ? nav.offsetHeight : 48; - const barsH = navH + (tb ? tb.offsetHeight : 46); - document.documentElement.style.setProperty('--nav-h', navH + 'px'); - document.documentElement.style.setProperty('--bars-h', barsH + 'px'); - } - window.addEventListener('resize', _setStickyOffsets); - document.addEventListener('DOMContentLoaded', () => { - _setStickyOffsets(); _loadChannelSchemas(); });