From 799276eaa13207096b52293eec073a8e2b159769 Mon Sep 17 00:00:00 2001 From: marvin Date: Sun, 13 Sep 2026 17:29:03 -0400 Subject: [PATCH] Redesign Live Health panel: status-driven accent bar, tile grid layout, live-check pulse indicator --- package.json | 2 +- src/public/app.js | 5 +++-- src/public/index.html | 14 +++++++------- src/public/styles.css | 4 ++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index e81353d..93efa50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "site-gateway", - "version": "0.11.51", + "version": "0.11.52", "private": true, "description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.", "type": "module", diff --git a/src/public/app.js b/src/public/app.js index 94ed030..d9a2cd5 100644 --- a/src/public/app.js +++ b/src/public/app.js @@ -124,6 +124,7 @@ function renderDashboard() { const overall = $("#overall-health"); overall.className = `health-badge ${hasErrors ? "error" : isChecking || hasNothingRunning ? "warning" : "healthy"}`; overall.textContent = hasErrors ? "Needs attention" : isChecking ? "Checking" : hasNothingRunning ? "Idle" : "Healthy"; + $("#health-panel").className = `dashboard-panel health-panel ${hasErrors ? "status-error" : isChecking || hasNothingRunning ? "status-warning" : "status-healthy"}`; $("#gateway-health-dot").className = `status-dot ${probeClass(data.gateway)}`; $("#gateway-health-copy").textContent = probeCopy(data.gateway, data.gateway.lastReload ? `Ready · reloaded ${formatTime(data.gateway.lastReload)}` : "Ready and responding", "Caddy is not responding"); $("#http-health-dot").className = `status-dot ${probeClass(data.services.http)}`; @@ -132,7 +133,7 @@ function renderDashboard() { $("#https-health-copy").textContent = probeCopy(data.services.https, `Ready and responding · ${data.services.https.activeDomains} TLS domain${data.services.https.activeDomains === 1 ? "" : "s"}`, "Not responding", "Not configured · no TLS domains enabled"); $("#storage-health-dot").className = `status-dot ${data.services.storage.healthy ? "running" : "error"}`; $("#storage-health-copy").textContent = data.services.storage.healthy ? "Ready · /data is readable and writable" : "Permission error · check /data"; - $("#health-checked").textContent = `Last checked ${formatTime(data.checkedAt)}`; + $("#health-checked").innerHTML = `Last checked ${formatTime(data.checkedAt)}`; updateDashboardUptime(data.system.uptimeSeconds); $("#system-memory").textContent = formatBytes(data.system.memoryBytes); $("#system-data").textContent = formatBytes(data.system.dataBytes); @@ -305,7 +306,7 @@ async function refreshPendingProxies(ids = []) { } } async function refreshDashboard() { - const button = $("#refresh-health"); button.disabled = true; button.classList.add("spinning"); $("#health-checked").textContent = "Checking services…"; + const button = $("#refresh-health"); button.disabled = true; button.classList.add("spinning"); $("#health-checked").innerHTML = 'Checking services…'; try { state.dashboard = await api("/api/dashboard"); renderDashboard(); } finally { button.disabled = false; button.classList.remove("spinning"); } } diff --git a/src/public/index.html b/src/public/index.html index 748602f..34eb561 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -84,15 +84,15 @@
Needs attention0No current issues
-
+

Live health

Services

Healthy
-
-
GatewayConfiguration valid
-
HTTP · Port 80Ready and responding
-
HTTPS · Port 443Not configured
-
Persistent storageData directory writable
+
+
GatewayConfiguration valid
+
HTTP · Port 80Ready and responding
+
HTTPS · Port 443Not configured
+
Persistent storageData directory writable
-

Last checked —

+

Last checked —

Runtime

System

diff --git a/src/public/styles.css b/src/public/styles.css index 526f109..ebb6902 100644 --- a/src/public/styles.css +++ b/src/public/styles.css @@ -22,7 +22,7 @@ main{padding-top:24px}.utility-bar{display:flex;align-items:center;justify-conte .dialog-card input:not([type="checkbox"]):not([type="file"]),.dialog-card select{font:inherit;width:100%;height:44px;min-height:44px;padding:0 12px;line-height:42px;border-width:1px}.aside-footer strong{color:var(--text);font-weight:750} /* Dashboard */ -.mobile-nav{display:none}.dashboard-view{margin-top:38px}.metric-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.metric-card{min-width:0;padding:20px;border:1px solid var(--line);border-radius:15px;background:linear-gradient(145deg,rgba(20,33,54,.95),rgba(13,23,39,.95));color:var(--text);text-align:left;position:relative;overflow:hidden}.metric-card::before{content:"";position:absolute;inset:0 0 auto 0;height:3px;background:var(--card-accent,var(--green));opacity:.85}.metric-card:focus{outline:none}.metric-card:focus-visible{outline:2px solid var(--green);outline-offset:-2px}.metric-card:is(button){cursor:pointer;transition:.2s}.metric-card:is(button):hover{transform:translateY(-2px);border-color:#3b5275;box-shadow:0 16px 40px rgba(0,0,0,.22)}.metric-card.accent-green{--card-accent:var(--green)}.metric-card.accent-blue{--card-accent:var(--blue)}.metric-card .metric-label,.metric-card>span:last-child{display:block}.metric-card .metric-label{color:var(--muted);font-size:.78rem;font-weight:700}.metric-card .metric-icon{position:absolute;top:16px;right:16px;width:32px;height:32px;display:grid;place-items:center;border-radius:9px;background:color-mix(in srgb,var(--card-accent,var(--muted)) 20%,transparent);color:var(--card-accent,var(--muted));font-size:.9rem;font-weight:800}.metric-card strong{display:block;margin:13px 0 7px;font-size:2rem;line-height:1}.metric-card>span:last-child{color:var(--muted);font-size:.76rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.metric-card.attention strong{color:var(--warning)}.metric-strip{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:12px}.metric-chip{display:flex;align-items:center;gap:12px;min-width:0;padding:13px 16px;border:1px solid var(--line);border-radius:13px;background:rgba(16,26,43,.76);color:var(--text);text-align:left;position:relative;overflow:hidden;transition:.2s}.metric-chip::before{content:"";position:absolute;inset:0 auto 0 0;width:3px;background:var(--card-accent,var(--muted));opacity:.9}.metric-chip:is(button){cursor:pointer}.metric-chip:is(button):hover{transform:translateY(-1px);border-color:#3b5275}.metric-chip:focus{outline:none}.metric-chip:focus-visible{outline:2px solid var(--green);outline-offset:-2px}.metric-chip .chip-icon{flex:0 0 auto;width:32px;height:32px;display:grid;place-items:center;border-radius:9px;background:color-mix(in srgb,var(--card-accent,var(--muted)) 20%,transparent);color:var(--card-accent,var(--muted));font-size:.85rem;font-weight:800}.metric-chip .chip-copy{display:flex;flex-direction:column;gap:2px;min-width:0}.metric-chip .metric-label{color:var(--muted);font-size:.72rem;font-weight:700}.metric-chip strong{font-size:1.3rem;line-height:1.1}.metric-chip small{color:var(--muted);font-size:.68rem}.metric-chip.accent-amber{--card-accent:var(--warning)}.metric-chip.accent-purple{--card-accent:#b59cff}.metric-chip.accent-warning{--card-accent:var(--warning)}.metric-chip.accent-warning strong{color:var(--warning)}.metric-chip.accent-green{--card-accent:var(--green)}.metric-chip.accent-green strong{color:var(--green)}.dashboard-columns{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:18px}.dashboard-columns.lower{align-items:start}.dashboard-panel{min-width:0;padding:22px;border:1px solid var(--line);border-radius:16px;background:rgba(16,26,43,.76)}.panel-heading{display:flex;align-items:center;justify-content:space-between;gap:15px;margin-bottom:20px}.panel-heading .eyebrow{margin-bottom:5px}.panel-heading h2{margin:0;font-size:1.2rem}.health-badge{padding:6px 9px;border-radius:999px;font-size:.7rem;font-weight:800}.health-badge.healthy{background:rgba(98,230,167,.12);color:var(--green)}.health-badge.warning{background:rgba(255,191,105,.12);color:var(--warning)}.health-badge.error{background:rgba(255,113,133,.12);color:var(--danger)}.health-list{display:grid;gap:3px}.health-list>div{display:flex;align-items:center;gap:13px;padding:13px 3px;border-top:1px solid var(--line)}.health-list>div:first-child{border-top:0}.health-list span:last-child,.dashboard-list-item span:last-child{display:grid;gap:3px}.health-list small,.dashboard-list-item small{color:var(--muted);font-size:.75rem}.system-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;margin:0}.system-grid div{min-width:0}.system-grid dt{color:var(--muted);font-size:.72rem}.system-grid dd{margin:6px 0 0;font-size:.9rem;font-weight:750;overflow:hidden;text-overflow:ellipsis}.dashboard-list{display:grid}.dashboard-list-item{display:flex;align-items:flex-start;gap:12px;padding:13px 3px;border-top:1px solid var(--line)}.dashboard-list-item:first-child{border-top:0}.dashboard-list-item strong{font-size:.82rem}.dashboard-list-item .status-dot{margin-top:4px;flex:0 0 auto}.activity-mark{display:grid;place-items:center;width:20px;height:20px;border-radius:50%;background:rgba(98,230,167,.12);color:var(--green);font-size:.7rem;font-weight:900}.quiet-state{color:var(--muted);font-size:.82rem;margin:3px 0} +.mobile-nav{display:none}.dashboard-view{margin-top:38px}.metric-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.metric-card{min-width:0;padding:20px;border:1px solid var(--line);border-radius:15px;background:linear-gradient(145deg,rgba(20,33,54,.95),rgba(13,23,39,.95));color:var(--text);text-align:left;position:relative;overflow:hidden}.metric-card::before{content:"";position:absolute;inset:0 0 auto 0;height:3px;background:var(--card-accent,var(--green));opacity:.85}.metric-card:focus{outline:none}.metric-card:focus-visible{outline:2px solid var(--green);outline-offset:-2px}.metric-card:is(button){cursor:pointer;transition:.2s}.metric-card:is(button):hover{transform:translateY(-2px);border-color:#3b5275;box-shadow:0 16px 40px rgba(0,0,0,.22)}.metric-card.accent-green{--card-accent:var(--green)}.metric-card.accent-blue{--card-accent:var(--blue)}.metric-card .metric-label,.metric-card>span:last-child{display:block}.metric-card .metric-label{color:var(--muted);font-size:.78rem;font-weight:700}.metric-card .metric-icon{position:absolute;top:16px;right:16px;width:32px;height:32px;display:grid;place-items:center;border-radius:9px;background:color-mix(in srgb,var(--card-accent,var(--muted)) 20%,transparent);color:var(--card-accent,var(--muted));font-size:.9rem;font-weight:800}.metric-card strong{display:block;margin:13px 0 7px;font-size:2rem;line-height:1}.metric-card>span:last-child{color:var(--muted);font-size:.76rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.metric-card.attention strong{color:var(--warning)}.metric-strip{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:12px}.metric-chip{display:flex;align-items:center;gap:12px;min-width:0;padding:13px 16px;border:1px solid var(--line);border-radius:13px;background:rgba(16,26,43,.76);color:var(--text);text-align:left;position:relative;overflow:hidden;transition:.2s}.metric-chip::before{content:"";position:absolute;inset:0 auto 0 0;width:3px;background:var(--card-accent,var(--muted));opacity:.9}.metric-chip:is(button){cursor:pointer}.metric-chip:is(button):hover{transform:translateY(-1px);border-color:#3b5275}.metric-chip:focus{outline:none}.metric-chip:focus-visible{outline:2px solid var(--green);outline-offset:-2px}.metric-chip .chip-icon{flex:0 0 auto;width:32px;height:32px;display:grid;place-items:center;border-radius:9px;background:color-mix(in srgb,var(--card-accent,var(--muted)) 20%,transparent);color:var(--card-accent,var(--muted));font-size:.85rem;font-weight:800}.metric-chip .chip-copy{display:flex;flex-direction:column;gap:2px;min-width:0}.metric-chip .metric-label{color:var(--muted);font-size:.72rem;font-weight:700}.metric-chip strong{font-size:1.3rem;line-height:1.1}.metric-chip small{color:var(--muted);font-size:.68rem}.metric-chip.accent-amber{--card-accent:var(--warning)}.metric-chip.accent-purple{--card-accent:#b59cff}.metric-chip.accent-warning{--card-accent:var(--warning)}.metric-chip.accent-warning strong{color:var(--warning)}.metric-chip.accent-green{--card-accent:var(--green)}.metric-chip.accent-green strong{color:var(--green)}.dashboard-columns{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:18px}.dashboard-columns.lower{align-items:start}.dashboard-panel{min-width:0;padding:22px;border:1px solid var(--line);border-radius:16px;background:rgba(16,26,43,.76)}.panel-heading{display:flex;align-items:center;justify-content:space-between;gap:15px;margin-bottom:20px}.panel-heading .eyebrow{margin-bottom:5px}.panel-heading h2{margin:0;font-size:1.2rem}.health-badge{padding:6px 9px;border-radius:999px;font-size:.7rem;font-weight:800}.health-badge.healthy{background:rgba(98,230,167,.12);color:var(--green)}.health-badge.warning{background:rgba(255,191,105,.12);color:var(--warning)}.health-badge.error{background:rgba(255,113,133,.12);color:var(--danger)}.health-panel{position:relative;overflow:hidden}.health-panel::before{content:"";position:absolute;inset:0 0 auto 0;height:3px;opacity:.9;transition:background-color .3s ease}.health-panel.status-healthy::before{background:var(--green)}.health-panel.status-warning::before{background:var(--warning)}.health-panel.status-error::before{background:var(--danger)}.health-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.health-tile{display:flex;align-items:center;gap:12px;min-width:0;padding:13px 14px;border:1px solid var(--line);border-radius:12px;background:rgba(8,16,29,.28);transition:.2s}.health-tile .status-dot{flex:0 0 auto}.health-tile-copy{display:grid;gap:3px;min-width:0}.health-tile-copy strong{font-size:.82rem}.health-tile-copy small{color:var(--muted);font-size:.72rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dashboard-list-item span:last-child{display:grid;gap:3px}.dashboard-list-item small{color:var(--muted);font-size:.75rem}.live-dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--green);margin-right:7px;vertical-align:middle;box-shadow:0 0 0 0 rgba(98,230,167,.6);animation:live-pulse 2.2s ease-in-out infinite}.live-dot.checking{background:var(--warning);animation-duration:.9s}@keyframes live-pulse{0%{box-shadow:0 0 0 0 rgba(98,230,167,.5)}70%{box-shadow:0 0 0 6px rgba(98,230,167,0)}100%{box-shadow:0 0 0 0 rgba(98,230,167,0)}}.system-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;margin:0}.system-grid div{min-width:0}.system-grid dt{color:var(--muted);font-size:.72rem}.system-grid dd{margin:6px 0 0;font-size:.9rem;font-weight:750;overflow:hidden;text-overflow:ellipsis}.dashboard-list{display:grid}.dashboard-list-item{display:flex;align-items:flex-start;gap:12px;padding:13px 3px;border-top:1px solid var(--line)}.dashboard-list-item:first-child{border-top:0}.dashboard-list-item strong{font-size:.82rem}.dashboard-list-item .status-dot{margin-top:4px;flex:0 0 auto}.activity-mark{display:grid;place-items:center;width:20px;height:20px;border-radius:50%;background:rgba(98,230,167,.12);color:var(--green);font-size:.7rem;font-weight:900}.quiet-state{color:var(--muted);font-size:.82rem;margin:3px 0} :root[data-theme="light"] .metric-card{background:linear-gradient(145deg,#fff,#f6f9fc)}:root[data-theme="light"] .dashboard-panel{background:rgba(255,255,255,.82)} @media(max-width:1100px){.metric-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.system-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:1100px){.metric-strip{grid-template-columns:repeat(2,minmax(0,1fr))}} @media(max-width:760px){.mobile-nav{display:grid;grid-template-columns:repeat(5,1fr);gap:5px;margin:0 0 30px;padding:4px;border:1px solid var(--line);border-radius:12px;background:var(--panel)}.mobile-nav button{justify-content:center;text-align:center;padding:9px 5px;font-size:.72rem}.dashboard-view{margin-top:30px}.metric-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.metric-card{padding:16px}.metric-card strong{font-size:1.65rem}.dashboard-columns{grid-template-columns:1fr}.system-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.metric-strip{grid-template-columns:1fr}.metric-chip{padding:11px 14px}} @@ -35,7 +35,7 @@ main{padding-top:24px}.utility-bar{display:flex;align-items:center;justify-conte .role-callout{display:grid;grid-template-columns:auto 1fr;gap:8px 16px;padding:18px 20px;margin-bottom:18px;border:1px solid var(--line);border-radius:14px;background:var(--panel);font-size:.8rem}.role-callout span{color:var(--muted)}.user-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(310px,1fr));gap:18px}.user-card{padding:20px;border:1px solid var(--line);border-radius:16px;background:linear-gradient(145deg,rgba(20,33,54,.95),rgba(13,23,39,.95))}.user-card-head{display:flex;align-items:center;justify-content:space-between}.user-avatar{width:42px;height:42px;display:grid;place-items:center;border-radius:12px;background:#1c3050;color:var(--blue);font-size:.78rem;font-weight:850}.user-card h2{margin:20px 0 5px;font-size:1.12rem}.user-card h2 small{padding:3px 6px;margin-left:5px;border-radius:999px;background:var(--panel2);color:var(--muted);font-size:.62rem;vertical-align:middle}.user-meta{display:grid;gap:5px;margin:18px 0;color:var(--muted);font-size:.75rem}.user-meta span:first-child{color:var(--text);font-weight:750}.user-actions{display:flex;flex-wrap:wrap;gap:8px}.user-actions .button{padding:8px 10px;font-size:.72rem}.danger-text{color:var(--danger)!important}:root[data-theme="light"] .user-card{background:linear-gradient(145deg,#fff,#f6f9fc)} @media(max-width:760px){.mobile-nav{grid-template-columns:repeat(3,1fr)}.role-callout{grid-template-columns:1fr}.role-callout strong:not(:first-child){margin-top:8px}.user-grid{grid-template-columns:1fr}} -@media(max-width:420px){.metric-grid{grid-template-columns:1fr}.system-grid{grid-template-columns:1fr 1fr}} +@media(max-width:420px){.metric-grid{grid-template-columns:1fr}.system-grid{grid-template-columns:1fr 1fr}.health-grid{grid-template-columns:1fr}} /* Health, menus, and locally cached service icons */ .health-actions{display:flex;align-items:center;gap:8px}.health-actions .icon-button{width:29px;height:29px;font-size:1rem}.checked-time{margin:14px 0 0;padding-top:13px;border-top:1px solid var(--line);color:var(--muted);font-size:.7rem}.spinning{animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.system-grid small{display:block;margin-top:4px;color:var(--muted);font-size:.65rem;line-height:1.35}.site-icon{overflow:hidden;padding:7px}.site-icon img{display:block;width:100%;height:100%;object-fit:contain}.menu button:focus-visible,.icon-choice:focus-visible{outline:2px solid var(--green);outline-offset:1px}.icon-dialog{width:min(720px,calc(100% - 28px))}.icon-picker{max-width:none}.icon-picker>.muted{margin-top:10px}.icon-results{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:9px;min-height:112px;max-height:330px;margin-top:16px;padding:2px;overflow:auto}.icon-results>.quiet-state{grid-column:1/-1;padding:28px 0;text-align:center}.icon-choice{display:grid;place-items:center;gap:8px;min-width:0;padding:12px 7px;border:1px solid var(--line);border-radius:11px;background:var(--panel2);color:var(--text);cursor:pointer}.icon-choice:hover{border-color:var(--green)}.icon-choice img{width:42px;height:42px;object-fit:contain}.icon-choice span{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.68rem}:root[data-theme="light"] .icon-choice{background:#f6f9fc}