Fix Gateway events table layout, merge Disk stats, color Runtime values by threshold

This commit is contained in:
2026-09-20 11:55:24 -04:00
parent 424ee26e00
commit 547f14d874
5 changed files with 18 additions and 10 deletions
+6 -4
View File
@@ -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 => `<div class="health-tile"><span class="status-dot ${row.ok ? "running" : "idle"}"></span><span class="health-tile-copy"><strong>${row.label}</strong><small>${row.detail}</small></span></div>`).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]) => `<div class="health-tile"><span class="status-dot running"></span><span class="health-tile-copy"><strong>${key[0].toUpperCase()}${key.slice(1)}</strong><small>${formatBytes(bytes)}</small></span></div>`).join("");
const capacity = store.capacity ? `<div class="health-tile"><span class="status-dot ${store.capacity.availableBytes / store.capacity.totalBytes > 0.1 ? "running" : "idle"}"></span><span class="health-tile-copy"><strong>Disk</strong><small>${formatBytes(store.capacity.availableBytes)} free of ${formatBytes(store.capacity.totalBytes)}</small></span></div>` : "";
storage.innerHTML = rows + capacity || '<p class="quiet-state">Storage usage unavailable.</p>';
storage.innerHTML = rows || '<p class="quiet-state">Storage usage unavailable.</p>';
}
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. */ }
+2 -2
View File
@@ -8,7 +8,7 @@
<title>Site Gateway</title>
<meta name="description" content="Host sites, proxy services, and manage HTTPS from one simple dashboard.">
<link rel="icon" type="image/png" href="/site-gateway-icon-approved.png">
<link rel="stylesheet" href="/styles.css?v=0.16.56">
<link rel="stylesheet" href="/styles.css?v=0.16.57">
</head>
<!-- ================================================================
@@ -451,6 +451,6 @@
<div id="toast" class="toast" role="status"></div>
<div id="update-banner" class="update-banner hidden" role="status"><span>A new version of Site Gateway is available.</span><div class="update-banner-actions"><button id="update-banner-refresh" class="button primary">Refresh</button><button id="update-banner-dismiss" class="text-button">Dismiss</button></div></div>
<!-- App scripts: core (app.js) then extended views/admin (features.js) -->
<script src="/app.js?v=0.16.56" defer></script><script src="/features.js?v=0.16.56" defer></script><script src="/select-enhance.js?v=0.16.56" defer></script>
<script src="/app.js?v=0.16.57" defer></script><script src="/features.js?v=0.16.57" defer></script><script src="/select-enhance.js?v=0.16.57" defer></script>
</body>
</html>
+7 -3
View File
@@ -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)}