From 547f14d874c0ddfc1d97ef1200fd05e9594fa49c Mon Sep 17 00:00:00 2001 From: Marvin Wade Date: Sun, 20 Sep 2026 11:55:24 -0400 Subject: [PATCH] Fix Gateway events table layout, merge Disk stats, color Runtime values by threshold --- ROADMAP.md | 2 ++ package.json | 2 +- src/public/features.js | 10 ++++++---- src/public/index.html | 4 ++-- src/public/styles.css | 10 +++++++--- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index c2156bf..acf0d3f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -236,3 +236,5 @@ Roughly in priority order: `v0.16.55` is a batch covering three items reported against the Certificates and Logs pages. First, the "Configure thresholds" link on the Certificates page moves from floating level with the page title down onto the same line as the descriptive text beneath it, using a dedicated `.section-heading-row` flex row instead of the whole heading block being one flex row. Second, the three-state Upstream coloring added to the Certificates table in v0.16.54 (healthy / monitoring paused or check pending / genuine failure) is now applied consistently everywhere else that shows the same `upstreamHealth` data -- the Hosted Site, Proxy Host, and Streaming Host cards' upstream text now also gets a matching amber `.upstream-copy.idle` state for "Monitoring paused" and "Upstream/Target check pending", instead of rendering identically green to a real healthy check the way it did before. Third, the Logs page's "Gateway events" panel is converted from a stacked list of `.event-row` cards into a sticky-header table (`Time | Severity | Category | Message`), matching the Access requests table directly above it on the same page; the Message column is left free to wrap rather than forced onto one line, since event messages are free-form and of varying length, unlike the fixed-format columns elsewhere. `v0.16.56` adds the two background jobs from v0.16.45 and v0.16.48 (the SQLite integrity re-scan and the /data disk-usage walk, both moved off the request path and onto their own timers to fix the multi-second dashboard freezes reported at the time) to the Scheduled jobs list shown on the Dashboard and Administration -> System page. Both caches already tracked their own `checkedAt` timestamp internally, they just were never surfaced in the `jobs` array both pages already render from -- "Database integrity check" (every 30 minutes) and "Disk usage refresh" (every 60 seconds) now appear alongside Upstream checks, Scheduled backups, Log pruning, Access-log import, Public IP check, and Configuration drift check, with live last-run timestamps the same as every other entry in that list. + +`v0.16.57` is a batch of four fixes/improvements against the Logs page and the System-runtime views reported after v0.16.55 and v0.16.56. First, the Gateway events table's Time/Severity/Category columns used percentage widths copied from the Performance table, leaving a lot of empty space around short values on wide screens; they're now fixed pixel widths (190px/110px/140px, same approach the Access requests table already uses), giving the free-form Message column the room it needs. Second, the Gateway events table wasn't visually joined to its heading/filter row above it the way Access requests and the Certificates table are -- the join CSS (`border-top:0`, bottom-only radius) that ties a heading box to the table below it existed for every other table on the app except this one, which is now fixed with the same one-line pattern. Third, the Runtime hero panel's Disk stat (shown on both the Dashboard and Administration -> System) now includes the real host free-space figure in its detail line alongside the assigned-quota percentage (e.g. "480 MB used of 2.0 GB assigned ยท 316 GB free on host") instead of only showing the quota view -- and the now-redundant standalone "Disk" tile in the System page's Disk usage breakdown panel (which showed the same host free/total figures with no quota context) has been removed, since that panel is otherwise scoped to what Site Gateway itself is storing (Sites, Backups, Certificates, Logs, Database). Fourth, the Runtime hero panel's CPU/Memory/Swap/Disk value text now gets the same amber/red coloring the progress bar underneath it already had at the existing 75%/90% thresholds -- previously only the thin bar changed color as a stat approached its limit, while the large percentage number stayed plain white regardless of severity. diff --git a/package.json b/package.json index d3fdb31..3da8202 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "site-gateway", - "version": "0.16.56", + "version": "0.16.57", "private": true, "description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.", "type": "module", diff --git a/src/public/features.js b/src/public/features.js index a0044a0..59cfb64 100644 --- a/src/public/features.js +++ b/src/public/features.js @@ -598,7 +598,7 @@ function heroSlotsMarkup(prefix, slots) { } function setHeroStat(prefix, key, { value, percent, detail, tone } = {}) { const valueEl = document.querySelector(`#${prefix}-${key}-value`), fillEl = document.querySelector(`#${prefix}-${key}-fill`), detailEl = document.querySelector(`#${prefix}-${key}-detail`); - if (valueEl) valueEl.textContent = value ?? "\u2014"; + if (valueEl) { valueEl.textContent = value ?? "\u2014"; valueEl.className = `system-hero-value${tone ? ` ${tone}` : ""}`; } if (fillEl) { fillEl.style.width = `${Math.max(0, Math.min(100, percent ?? 0))}%`; fillEl.className = `system-hero-fill${tone ? ` ${tone}` : ""}`; } if (detailEl) detailEl.textContent = detail || ""; } @@ -632,7 +632,7 @@ function renderHeroPanel(prefix, health, { sixthSlot = "throughput" } = {}) { else setHeroStat(prefix, "swap", { value: "\u2014", detail: "cgroup swap stats unavailable" }); if (health.disk) { const overAssigned = health.disk.assignedLimitBytes && health.disk.percent > 100; - const diskDetail = health.disk.assignedLimitBytes ? `${formatBytes(health.disk.usedBytes)} used of ${formatBytes(health.disk.assignedLimitBytes)} assigned` : `${formatBytes(health.disk.usedBytes)} used \u00b7 ${formatBytes(health.disk.availableBytes)} free`; + const diskDetail = health.disk.assignedLimitBytes ? `${formatBytes(health.disk.usedBytes)} used of ${formatBytes(health.disk.assignedLimitBytes)} assigned \u00b7 ${formatBytes(health.disk.availableBytes)} free on host` : `${formatBytes(health.disk.usedBytes)} used \u00b7 ${formatBytes(health.disk.availableBytes)} free`; setHeroStat(prefix, "disk", { value: `${health.disk.percent.toFixed(1)}%`, percent: Math.min(100, health.disk.percent), tone: overAssigned ? "critical" : tone(health.disk.percent), detail: diskDetail }); } else setHeroStat(prefix, "disk", { value: "\u2014", detail: "Disk stats unavailable" }); @@ -764,9 +764,11 @@ async function renderSystemStatus(panel) { { ok: sec.acmeEmailConfigured, label: "ACME_EMAIL", detail: sec.acmeEmailConfigured ? "Configured." : "Not set \u2014 certificate issuance will proceed without a registration contact." }, ].map(row => `
${row.label}${row.detail}
`).join(""); if (storage) { + // Host disk capacity (free/total) now lives on the Runtime hero panel's Disk stat detail + // line instead of duplicating it here -- this breakdown stays scoped to what Site Gateway + // itself is storing (Sites, Backups, Certificates, Logs, Database). const rows = Object.entries(store.breakdown || {}).map(([key, bytes]) => `
${key[0].toUpperCase()}${key.slice(1)}${formatBytes(bytes)}
`).join(""); - const capacity = store.capacity ? `
Disk${formatBytes(store.capacity.availableBytes)} free of ${formatBytes(store.capacity.totalBytes)}
` : ""; - storage.innerHTML = rows + capacity || '

Storage usage unavailable.

'; + storage.innerHTML = rows || '

Storage usage unavailable.

'; } if (restartButton) { restartButton.disabled = !policy.restartAvailable; if (restartStatus) restartStatus.textContent = policy.reason || (policy.policyName ? `Restart policy: ${policy.policyName}.` : ""); } } catch { /* Status widgets keep their last-known values if a refresh call fails. */ } diff --git a/src/public/index.html b/src/public/index.html index 275a120..39b1dc3 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -8,7 +8,7 @@ Site Gateway - + - + diff --git a/src/public/styles.css b/src/public/styles.css index 173ab9d..e5e9abe 100644 --- a/src/public/styles.css +++ b/src/public/styles.css @@ -256,10 +256,11 @@ header{align-items:flex-end} .upstream-copy.idle{color:var(--warning)} .event-table-wrap{max-height:min(52vh,620px);overflow:auto;margin-top:var(--space-4)} .event-table-wrap .event-table thead th{position:sticky;top:0;background:var(--panel);z-index:1} -.event-table th:nth-child(1),.event-table td:nth-child(1){width:15%;white-space:nowrap} -.event-table th:nth-child(2),.event-table td:nth-child(2){width:11%;white-space:nowrap} -.event-table th:nth-child(3),.event-table td:nth-child(3){width:14%;white-space:nowrap} +.event-table th:nth-child(1),.event-table td:nth-child(1){width:190px;white-space:nowrap} +.event-table th:nth-child(2),.event-table td:nth-child(2){width:110px;white-space:nowrap} +.event-table th:nth-child(3),.event-table td:nth-child(3){width:140px;white-space:nowrap} .event-table th:nth-child(4),.event-table td:nth-child(4){white-space:normal;overflow-wrap:anywhere} +@media(max-width:900px){.event-table th:nth-child(1),.event-table td:nth-child(1){width:150px}} .event-table td .status-dot{margin-right:6px;vertical-align:-1px} .activity-mark.bad{background:rgba(var(--danger-rgb),.12);color:var(--danger)} .feature-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin-bottom:18px} @@ -848,6 +849,7 @@ select{appearance:none!important;-webkit-appearance:none!important;background-re .readiness-panel,.log-activity{background:transparent;border:0;padding:0} .readiness-panel .panel-heading,.log-activity .panel-heading{margin:20px 0 0;padding:18px 20px;border:1px solid var(--line);border-bottom:0;border-radius:var(--radius-2xl) 15px 0 0;background:var(--panel)} .log-activity .event-filters{margin:0;padding:0 20px 15px;border-left:1px solid var(--line);border-right:1px solid var(--line);background:var(--panel)} +.log-activity .event-table-wrap{border-top:0;border-radius:0 0 15px 15px;margin-top:0} .log-activity .diagnostic-list{border:1px solid var(--line);border-radius:0 0 15px 15px;background:var(--panel);overflow:auto} .log-activity .event-row{gap:var(--space-4);padding:18px 20px;align-items:center} .log-activity .event-row .status-dot{flex:0 0 8px} @@ -1009,6 +1011,8 @@ select{appearance:none!important;-webkit-appearance:none!important;background-re .system-hero-stat{min-width:0;display:flex;flex-direction:column;gap:6px} .system-hero-label{color:var(--muted);font-size:var(--font-size-sm);font-weight:650;text-transform:uppercase;letter-spacing:.04em} .system-hero-value{font-size:1.5rem;font-weight:800;line-height:1.1} +.system-hero-value.warning{color:var(--warning)} +.system-hero-value.critical{color:var(--danger)} .system-hero-bar{height:6px;border-radius:var(--radius-full);background:rgba(var(--bg-rgb),.4);overflow:hidden} .system-hero-fill{height:100%;border-radius:var(--radius-full);background:var(--green);transition:width .4s ease} .system-hero-fill.warning{background:var(--warning)}