Add System tab hero panel auto-refresh and DATA_DIR_LIMIT_GB disk allowance
This commit is contained in:
+15
-1
@@ -600,7 +600,11 @@ function renderSystemHealthHero(health) {
|
||||
else if (health.swap && health.swap.limitBytes) setHeroStat("swap", { value: `${health.swap.percent.toFixed(1)}%`, percent: health.swap.percent, tone: tone(health.swap.percent), detail: `${formatBytes(health.swap.usedBytes)} / ${formatBytes(health.swap.limitBytes)}` });
|
||||
else if (health.swap) setHeroStat("swap", { value: formatBytes(health.swap.usedBytes), percent: 0, detail: "Unlimited \u2014 shares host swap" });
|
||||
else setHeroStat("swap", { value: "\u2014", detail: "cgroup swap stats unavailable" });
|
||||
if (health.disk) setHeroStat("disk", { value: `${health.disk.percent.toFixed(1)}%`, percent: health.disk.percent, tone: tone(health.disk.percent), detail: `${formatBytes(health.disk.usedBytes)} used \u00b7 ${formatBytes(health.disk.availableBytes)} free` });
|
||||
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`;
|
||||
setHeroStat("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("disk", { value: "\u2014", detail: "Disk stats unavailable" });
|
||||
if (health.network) setHeroStat("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("network", { value: "\u2014", detail: "Sampling\u2026" });
|
||||
@@ -664,6 +668,16 @@ function renderSystemPanel() {
|
||||
}
|
||||
catch (error) { toast(error.message, "error"); button.disabled = false; button.textContent = "Restart application"; }
|
||||
});
|
||||
// Keep the hero panel's live numbers current while the System tab is actually visible --
|
||||
// a lightweight direct poll of /api/system/health, not a full refresh() (which also
|
||||
// refetches sites/proxies/certificates/etc.), so it stays cheap even on a fast interval.
|
||||
// Stops itself from doing any work (skips the fetch) once the tab isn't in view, mirroring
|
||||
// the guard the Dashboard's own health timer already uses for the same reason.
|
||||
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(renderSystemHealthHero).catch(() => {});
|
||||
}, 7000);
|
||||
}
|
||||
renderSystemStatus(panel);
|
||||
}
|
||||
|
||||
@@ -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.30">
|
||||
<link rel="stylesheet" href="/styles.css?v=0.16.31">
|
||||
</head>
|
||||
|
||||
<!-- ================================================================
|
||||
@@ -434,6 +434,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.30" defer></script><script src="/features.js?v=0.16.30" defer></script><script src="/select-enhance.js?v=0.16.30" defer></script>
|
||||
<script src="/app.js?v=0.16.31" defer></script><script src="/features.js?v=0.16.31" defer></script><script src="/select-enhance.js?v=0.16.31" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+12
-1
@@ -199,7 +199,18 @@ async function systemHealthSnapshot() {
|
||||
cpu,
|
||||
memory,
|
||||
swap,
|
||||
disk: disk ? { totalBytes: disk.blocks * disk.bsize, freeBytes: disk.bfree * disk.bsize, availableBytes: disk.bavail * disk.bsize, usedBytes: disk.blocks * disk.bsize - disk.bfree * disk.bsize, percent: ((disk.blocks - disk.bfree) / disk.blocks) * 100 } : null,
|
||||
disk: disk ? (() => {
|
||||
const totalBytes = disk.blocks * disk.bsize, freeBytes = disk.bfree * disk.bsize, availableBytes = disk.bavail * disk.bsize, usedBytes = totalBytes - freeBytes;
|
||||
// DATA_DIR_LIMIT_GB lets an operator tell the hero panel what's actually assigned to this
|
||||
// deployment (e.g. a dedicated share/zvol sized smaller than the whole host volume), since
|
||||
// Docker has no real per-container disk-space quota to read the way it does for CPU/memory.
|
||||
// Purely a display denominator -- it doesn't enforce anything -- so usage over 100% is a
|
||||
// real, meaningful warning rather than a bug: it means actual usage has exceeded what was assigned.
|
||||
const assignedLimitGb = numberEnv("DATA_DIR_LIMIT_GB", null);
|
||||
const assignedLimitBytes = assignedLimitGb && assignedLimitGb > 0 ? assignedLimitGb * 1024 ** 3 : null;
|
||||
const denominatorBytes = assignedLimitBytes || totalBytes;
|
||||
return { totalBytes, freeBytes, availableBytes, usedBytes, assignedLimitBytes, percent: (usedBytes / denominatorBytes) * 100 };
|
||||
})() : null,
|
||||
network: networkRate,
|
||||
throughput: { liveRequests: storage.performanceLiveCount(60) },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user