Simplify Dashboard Uptime onto the shared 7-second hero poll
This commit is contained in:
+8
-8
@@ -147,7 +147,6 @@ function probeCopy(service, ready, error, unconfigured = "Not configured") {
|
||||
|
||||
|
||||
function renderDashboardJobs(system) { const columns = document.querySelector("#dashboard-view .dashboard-columns"), health = columns?.firstElementChild; if (!columns) return; let panel = document.querySelector("#dashboard-jobs"); if (!panel) { panel = document.createElement("section"); panel.id = "dashboard-jobs"; panel.className = "dashboard-panel dashboard-jobs-panel"; columns.insertBefore(panel, columns.children[1] || null); } if (health && health.parentElement === columns) columns.parentElement.insertBefore(health, columns); panel.innerHTML = `<div class="panel-heading"><div><p class="eyebrow">Operations</p><h2>Scheduled jobs</h2></div></div><div class="dashboard-jobs-list">${(system.jobs || []).map(job => `<div class="dashboard-list-item"><span class="status-dot ${job.enabled ? "running" : "idle"}"></span><span><strong>${escapeHtml(job.name)}</strong><small>${job.enabled ? `Active · ${escapeHtml(job.schedule)}` : "Disabled"}</small></span></div>`).join("")}</div>`; }
|
||||
function updateDashboardUptime(seconds) { if (Number.isFinite(seconds)) window.__dashboardStartedAt = Date.now() - seconds * 1000; const started = window.__dashboardStartedAt; const target = document.querySelector("#system-uptime"); if (!target || started === undefined) return; const elapsed = Math.max(0, Math.floor((Date.now() - started) / 1000)); target.textContent = formatDuration(elapsed); }
|
||||
// Dashboard tiles share one baseline accent (green) and switch to the existing
|
||||
// --warning / --danger tokens when the thing they count is actually in trouble --
|
||||
// the same mechanism the "Needs attention" chip already used.
|
||||
@@ -214,7 +213,6 @@ function renderDashboard() {
|
||||
$("#upstream-health-dot").className = `status-dot ${!upstreams.total ? "inactive" : upstreams.unhealthy > 0 ? "error" : "running"}`;
|
||||
$("#upstream-health-copy").textContent = !upstreams.total ? "No proxy hosts configured" : `${upstreams.healthy} of ${upstreams.total} healthy`;
|
||||
$("#health-checked").innerHTML = `<span class="live-dot" id="health-live-dot"></span>Last checked ${formatTime(data.checkedAt)}`;
|
||||
updateDashboardUptime(data.system.uptimeSeconds);
|
||||
// Memory/Data/Storage/Version/Database/Public IP moved to the Administration > System tab's
|
||||
// Version panel -- the Dashboard's own Runtime/System panel is now the shared hero component
|
||||
// (see renderHeroPanel/refreshDashboardHero), which reads real container-scoped CPU/memory/
|
||||
@@ -228,7 +226,6 @@ function renderDashboard() {
|
||||
$("#activity-list").innerHTML = data.activity.length ? data.activity.slice(0, 5).map(item => `<div class="activity-tile"><span class="activity-mark ${item.status === "error" ? "bad" : item.status === "warning" ? "warn" : ""}">${item.status === "error" || item.status === "warning" ? "!" : "✓"}</span><span class="activity-copy"><strong>${escapeHtml(item.message)}</strong><small title="${escapeHtml(formatTime(item.at))}">${escapeHtml(formatRelativeTime(item.at))}</small></span></div>`).join("") : '<p class="quiet-state">No recent activity.</p>';
|
||||
}
|
||||
|
||||
setInterval(() => { if (!document.querySelector("#dashboard-view.hidden")) updateDashboardUptime(); }, 1000);
|
||||
|
||||
|
||||
// --- Card rendering helpers (icons, permissions) -----------------------------------
|
||||
@@ -550,17 +547,20 @@ async function refreshDashboard() {
|
||||
try { state.dashboard = await api("/api/dashboard"); renderDashboard(); }
|
||||
finally { button.disabled = false; button.classList.remove("spinning"); }
|
||||
}
|
||||
// Populates the Dashboard's hero panel (CPU/memory/swap/disk/network -- Throughput is skipped
|
||||
// here since the Dashboard already has its own live-requests chip, and Uptime is handled by the
|
||||
// existing updateDashboardUptime() ticker rather than this endpoint) directly from
|
||||
// Populates the Dashboard's hero panel (CPU/memory/swap/disk/network/uptime) directly from
|
||||
// /api/system/health, the same call and the same renderHeroPanel() the Administration > System
|
||||
// tab's hero uses, so the two can never show different numbers for the same live stat again.
|
||||
// Uptime uses sixthSlot: "uptime" here (the System tab uses the default "throughput" slot instead,
|
||||
// since the Dashboard already has its own live-requests chip elsewhere -- see below). There's no
|
||||
// separate ticker or anchor for Uptime anymore: formatDuration() only ever shows minute-level
|
||||
// granularity, so refreshing it on this same 7s poll as everything else is all the precision the
|
||||
// display needs, and it removes a whole class of ticker/anchor race-condition bugs for free.
|
||||
async function refreshDashboardHero() {
|
||||
try {
|
||||
const health = await api("/api/system/health");
|
||||
window.renderHeroPanel?.("dashboard-hero", health, { includeThroughput: false });
|
||||
window.renderHeroPanel?.("dashboard-hero", health, { sixthSlot: "uptime" });
|
||||
// /api/system/health already computes throughput.liveRequests (the hero just doesn't display
|
||||
// it here, since the Dashboard shows it in its own chip instead -- see includeThroughput above).
|
||||
// it here, since the Dashboard shows it in its own chip instead -- see sixthSlot above).
|
||||
// Reuse that number to keep the chip on the same 7s cadence as the hero, instead of leaving it
|
||||
// on the separate 30s refreshDashboard() timer, which was the actual bug being reported here.
|
||||
const throughputTotal = $("#dash-throughput-total"); if (throughputTotal && health.throughput) throughputTotal.textContent = health.throughput.liveRequests ?? 0;
|
||||
|
||||
+10
-5
@@ -595,8 +595,12 @@ function setHeroStat(prefix, key, { value, percent, detail, tone } = {}) {
|
||||
// isn't available (e.g. no cgroup v2, no readable network interfaces, swap disabled on the host).
|
||||
// Throughput is System-tab-only -- the Dashboard already shows live requests/min in its own chip,
|
||||
// so `includeThroughput: false` there skips it rather than showing the same number twice.
|
||||
function renderHeroPanel(prefix, health, { includeThroughput } = { includeThroughput: true }) {
|
||||
const keys = includeThroughput ? ["cpu", "memory", "swap", "disk", "network", "throughput"] : ["cpu", "memory", "swap", "disk", "network"];
|
||||
function renderHeroPanel(prefix, health, { sixthSlot = "throughput" } = {}) {
|
||||
// sixthSlot picks what the panel's sixth stat is: "throughput" (Administration > System, since
|
||||
// that page has no other requests/min display) or "uptime" (the Dashboard, which already has
|
||||
// its own Throughput chip elsewhere -- showing it twice added nothing). Both come straight off
|
||||
// the same /api/system/health poll as everything else here, no separate ticker or anchor.
|
||||
const keys = ["cpu", "memory", "swap", "disk", "network", sixthSlot];
|
||||
if (!document.querySelector(`#${prefix}-${keys[0]}-value`)) return;
|
||||
if (!health) { keys.forEach(key => setHeroStat(prefix, key, { value: "\u2014", detail: "Unavailable" })); return; }
|
||||
const tone = percent => percent >= 90 ? "critical" : percent >= 75 ? "warning" : "";
|
||||
@@ -622,7 +626,8 @@ function renderHeroPanel(prefix, health, { includeThroughput } = { includeThroug
|
||||
else setHeroStat(prefix, "disk", { value: "\u2014", detail: "Disk stats unavailable" });
|
||||
if (health.network) setHeroStat(prefix, "network", { value: formatRate(health.network.rxBytesPerSec + health.network.txBytesPerSec), percent: 0, detail: `\u2193 ${formatRate(health.network.rxBytesPerSec)} \u00b7 \u2191 ${formatRate(health.network.txBytesPerSec)}` });
|
||||
else setHeroStat(prefix, "network", { value: "\u2014", detail: "Sampling\u2026" });
|
||||
if (includeThroughput) setHeroStat(prefix, "throughput", { value: String(health.throughput?.liveRequests ?? 0), percent: 0, detail: "requests in the last minute" });
|
||||
if (sixthSlot === "throughput") setHeroStat(prefix, "throughput", { value: String(health.throughput?.liveRequests ?? 0), percent: 0, detail: "requests in the last minute" });
|
||||
else if (sixthSlot === "uptime") setHeroStat(prefix, "uptime", Number.isFinite(health.uptimeSeconds) ? { value: formatDuration(health.uptimeSeconds), detail: "Since last restart" } : { value: "\u2014", detail: "Unavailable" });
|
||||
}
|
||||
// --- System tab: environment/integration status, storage, scheduled jobs, sync, restart --------
|
||||
function renderSystemPanel() {
|
||||
@@ -686,7 +691,7 @@ function renderSystemPanel() {
|
||||
if (!state.systemHealthTimer) state.systemHealthTimer = setInterval(() => {
|
||||
const systemPanel = document.querySelector('[data-admin-panel="system"]');
|
||||
if (state.view !== "administration" || !systemPanel || systemPanel.classList.contains("hidden")) return;
|
||||
api("/api/system/health").then(health => renderHeroPanel("system-hero", health, { includeThroughput: true })).catch(() => {});
|
||||
api("/api/system/health").then(health => renderHeroPanel("system-hero", health)).catch(() => {});
|
||||
}, 7000);
|
||||
}
|
||||
renderSystemStatus(panel);
|
||||
@@ -728,7 +733,7 @@ async function renderSystemStatus(panel) {
|
||||
api("/api/system/restart-policy"),
|
||||
api("/api/system/health").catch(() => null),
|
||||
]);
|
||||
renderHeroPanel("system-hero", health, { includeThroughput: true });
|
||||
renderHeroPanel("system-hero", health);
|
||||
if (security) security.innerHTML = [
|
||||
{ ok: !sec.adminPasswordIsDefault, label: "ADMIN_PASSWORD", detail: sec.adminPasswordIsDefault ? "Still using the built-in default \u2014 set this before exposing the dashboard." : "Configured." },
|
||||
{ ok: !sec.sessionSecretIsDefault, label: "SESSION_SECRET", detail: sec.sessionSecretIsDefault ? "Not set \u2014 sessions are keyed off the admin credentials instead of an independent secret." : "Configured." },
|
||||
|
||||
@@ -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.38">
|
||||
<link rel="stylesheet" href="/styles.css?v=0.16.39">
|
||||
</head>
|
||||
|
||||
<!-- ================================================================
|
||||
@@ -138,7 +138,7 @@
|
||||
<div class="system-hero-stat" data-hero-stat="swap"><span class="system-hero-label">Swap</span><strong class="system-hero-value" id="dashboard-hero-swap-value">—</strong><div class="system-hero-bar"><div class="system-hero-fill" id="dashboard-hero-swap-fill"></div></div><small class="system-hero-detail" id="dashboard-hero-swap-detail"></small></div>
|
||||
<div class="system-hero-stat" data-hero-stat="disk"><span class="system-hero-label">Disk</span><strong class="system-hero-value" id="dashboard-hero-disk-value">—</strong><div class="system-hero-bar"><div class="system-hero-fill" id="dashboard-hero-disk-fill"></div></div><small class="system-hero-detail" id="dashboard-hero-disk-detail"></small></div>
|
||||
<div class="system-hero-stat" data-hero-stat="network"><span class="system-hero-label">Network</span><strong class="system-hero-value" id="dashboard-hero-network-value">—</strong><div class="system-hero-bar"><div class="system-hero-fill" id="dashboard-hero-network-fill"></div></div><small class="system-hero-detail" id="dashboard-hero-network-detail"></small></div>
|
||||
<div class="system-hero-stat" data-hero-stat="uptime"><span class="system-hero-label">Uptime</span><strong class="system-hero-value" id="system-uptime">—</strong><div class="system-hero-bar"></div><small class="system-hero-detail">Since last restart</small></div>
|
||||
<div class="system-hero-stat" data-hero-stat="uptime"><span class="system-hero-label">Uptime</span><strong class="system-hero-value" id="dashboard-hero-uptime-value">—</strong><div class="system-hero-bar"><div class="system-hero-fill" id="dashboard-hero-uptime-fill"></div></div><small class="system-hero-detail" id="dashboard-hero-uptime-detail"></small></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -432,6 +432,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.38" defer></script><script src="/features.js?v=0.16.38" defer></script><script src="/select-enhance.js?v=0.16.38" defer></script>
|
||||
<script src="/app.js?v=0.16.39" defer></script><script src="/features.js?v=0.16.39" defer></script><script src="/select-enhance.js?v=0.16.39" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user