Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e6a5f9196 |
@@ -12,7 +12,7 @@
|
||||
<img alt="Docker" src="https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white">
|
||||
<img alt="Architectures" src="https://img.shields.io/badge/platform-amd64%20%7C%20arm64-5965F2">
|
||||
<img alt="Caddy" src="https://img.shields.io/badge/powered%20by-Caddy-1F88C0">
|
||||
<img alt="Version" src="https://img.shields.io/badge/version-0.16.39-62E6A7">
|
||||
<img alt="Version" src="https://img.shields.io/badge/version-0.16.40-62E6A7">
|
||||
</p>
|
||||
<p>
|
||||
<a href="#why-site-gateway">Why Site Gateway</a> ·
|
||||
|
||||
@@ -205,3 +205,6 @@ Roughly in priority order:
|
||||
|
||||
|
||||
`v0.16.39` retires the Dashboard Uptime tile's separate 1-second client-side ticker, the same mechanism behind the v0.16.38 "resets to 0m on page load" bug, in favor of treating Uptime as just another field on the shared 7-second `/api/system/health` poll that already drives CPU, memory, swap, disk, and network on both the Dashboard and the Administration > System tab's hero panel. Revisiting the ticker after fixing its race condition, it turned out to be solving a precision problem the display doesn't actually have: `formatDuration()` only ever renders minute-level granularity ("2h 59m"), never seconds, so a per-second tick never changed what was on screen between one 7-second poll and the next. `systemHealthSnapshot()` now includes `uptimeSeconds` (from `process.uptime()`, the same source `dashboardSnapshot()` already used), and `renderHeroPanel()`'s former `includeThroughput` boolean became a `sixthSlot` option ("throughput" for the System tab, which has no other requests/min display, or "uptime" for the Dashboard, which already shows Throughput in its own chip) so the sixth hero slot can be either stat without a special case. The client ticker, its anchor state, and the whole race-condition class it enabled are gone: one poll, one code path, six stats, no anchor to get out of sync.
|
||||
|
||||
|
||||
`v0.16.40` moves the Administration → System tab's Version panel Uptime figure onto the same 7-second `/api/system/health` poll driving the hero panel above it, instead of only updating when the page's slower, general dashboard snapshot refetches (on load, or every ~30 seconds while the Dashboard view specifically is active). Found while reviewing the v0.16.39 change: the hero's own Uptime slot was now current to the second, but the separate Version-panel line right below it — the same number, shown twice on the same page — could still be stale by up to half a minute or more. `renderSystemStatus()`'s Uptime is now wrapped in its own `#system-version-uptime` span and updated by a small `updateSystemVersionUptime(health)` helper, called both from the initial render and from the same 7-second timer that already refreshes the hero, so the two Uptime figures on that page can no longer drift apart.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "site-gateway",
|
||||
"version": "0.16.39",
|
||||
"version": "0.16.40",
|
||||
"private": true,
|
||||
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
|
||||
"type": "module",
|
||||
|
||||
+18
-6
@@ -691,11 +691,19 @@ 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)).catch(() => {});
|
||||
api("/api/system/health").then(health => { renderHeroPanel("system-hero", health); updateSystemVersionUptime(health); }).catch(() => {});
|
||||
}, 7000);
|
||||
}
|
||||
renderSystemStatus(panel);
|
||||
}
|
||||
// Updates just the Version panel's Uptime figure from a fresh /api/system/health payload --
|
||||
// kept separate from the rest of renderSystemStatus() so it can be called on the fast 7s hero
|
||||
// poll without re-rendering (or re-fetching) everything else in that panel.
|
||||
function updateSystemVersionUptime(health) {
|
||||
const el = document.querySelector("#system-version-uptime");
|
||||
if (!el) return;
|
||||
el.textContent = Number.isFinite(health?.uptimeSeconds) ? formatDuration(health.uptimeSeconds) : "Unavailable";
|
||||
}
|
||||
async function renderSystemStatus(panel) {
|
||||
panel = panel || document.querySelector('[data-admin-panel="system"]');
|
||||
if (!panel) return;
|
||||
@@ -715,16 +723,19 @@ async function renderSystemStatus(panel) {
|
||||
}
|
||||
if (syncStatus) { const drift = (state.dashboard?.attention || []).some(item => item.kind === "drift"); syncStatus.textContent = drift ? "Configuration drift detected \u2014 the running gateway no longer matches the last known-good configuration." : `Gateway configuration is in sync. Last reload: ${state.dashboard?.gateway?.lastReload ? formatTime(state.dashboard.gateway.lastReload) : "unknown"}.`; syncStatus.className = drift ? "muted status-warning" : "muted"; }
|
||||
if (version) {
|
||||
// Uptime, Caddy version, Database status, and Public IP used to live on the Dashboard's
|
||||
// Runtime/System panel -- that panel is now the shared hero component (CPU/memory/swap/disk/
|
||||
// network), so this operational metadata moved here instead, reusing the same system.* fields
|
||||
// from the global dashboard snapshot rather than a separate fetch.
|
||||
// Caddy version, Database status, and Public IP used to live on the Dashboard's Runtime/System
|
||||
// panel -- that panel is now the shared hero component (CPU/memory/swap/disk/network), so this
|
||||
// operational metadata moved here instead, reusing the same system.* fields from the global
|
||||
// dashboard snapshot rather than a separate fetch. Uptime is the one exception: it's wrapped in
|
||||
// its own #system-version-uptime span and kept current by updateSystemVersionUptime(), called
|
||||
// from the same 7-second /api/system/health poll that drives the hero panel above, instead of
|
||||
// only refreshing on the slower ~30s dashboard snapshot like the rest of this block.
|
||||
const sys = state.dashboard?.system || {};
|
||||
const uptime = Number.isFinite(sys.uptimeSeconds) ? formatDuration(sys.uptimeSeconds) : "Unavailable";
|
||||
const database = sys.databaseEngine ? `${extendedEscape(sys.databaseEngine)} \u00b7 ${extendedEscape(sys.databaseStatus || "unknown")} \u00b7 ${formatBytes(sys.databaseBytes)}` : "Unavailable";
|
||||
const publicIp = sys.publicIp || (sys.publicIpError ? "Unavailable" : "Checking\u2026");
|
||||
const publicIpDetail = sys.publicIpError ? `check failed \u00b7 ${extendedEscape(sys.publicIpError)}` : sys.publicIpCheckedAt ? `checked ${extendedEscape(formatTime(sys.publicIpCheckedAt))}` : "not yet checked";
|
||||
version.innerHTML = `Site Gateway v${extendedEscape(state.config?.version || "unknown")} \u00b7 Caddy ${extendedEscape(sys.caddyVersion || "unknown")}<br>Uptime: ${uptime} \u00b7 Database: ${database} \u00b7 Public IP: ${extendedEscape(publicIp)} (${publicIpDetail})<br>Access this dashboard at: <code>${extendedEscape(location.origin)}</code><br>Data directory: <code>${extendedEscape(state.config?.storage?.databasePath ? state.config.storage.databasePath.replace(/\/database\/.*/, "") : "/data")}</code> · Site ports: <code>${extendedEscape(String(state.config?.minPort ?? ""))}\u2013${extendedEscape(String(state.config?.maxPort ?? ""))}</code>`;
|
||||
version.innerHTML = `Site Gateway v${extendedEscape(state.config?.version || "unknown")} \u00b7 Caddy ${extendedEscape(sys.caddyVersion || "unknown")}<br>Uptime: <span id="system-version-uptime">${uptime}</span> \u00b7 Database: ${database} \u00b7 Public IP: ${extendedEscape(publicIp)} (${publicIpDetail})<br>Access this dashboard at: <code>${extendedEscape(location.origin)}</code><br>Data directory: <code>${extendedEscape(state.config?.storage?.databasePath ? state.config.storage.databasePath.replace(/\/database\/.*/, "") : "/data")}</code> · Site ports: <code>${extendedEscape(String(state.config?.minPort ?? ""))}\u2013${extendedEscape(String(state.config?.maxPort ?? ""))}</code>`;
|
||||
}
|
||||
try {
|
||||
const [sec, store, policy, health] = await Promise.all([
|
||||
@@ -734,6 +745,7 @@ async function renderSystemStatus(panel) {
|
||||
api("/api/system/health").catch(() => null),
|
||||
]);
|
||||
renderHeroPanel("system-hero", health);
|
||||
if (health) updateSystemVersionUptime(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." },
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user