Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2577493225 | |||
| 23784accde | |||
| cb94e5c63c | |||
| 157c680e58 |
@@ -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.3-62E6A7">
|
||||
<img alt="Version" src="https://img.shields.io/badge/version-0.16.7-62E6A7">
|
||||
</p>
|
||||
<p>
|
||||
<a href="#why-site-gateway">Why Site Gateway</a> ·
|
||||
|
||||
@@ -134,3 +134,11 @@ Roughly in priority order:
|
||||
- Arbitrary Caddy snippets substantially increase support and security risk and stay an expert-only, size-limited, validated feature.
|
||||
|
||||
`v0.16.3` fixes the System tab's real population and layout bugs reported after v0.16.2 went live. Root cause of the empty sections and the tab "flickering" on refresh: the System panel is created dynamically (like Groups, Audit log, and API Access) the first time `refresh()` runs after login, but the app's render order calls the tab-visibility toggle *before* that panel exists — so a brand-new panel is created already carrying the `hidden` class, and `renderSystemStatus()` was guarded to skip populating anything while its panel was hidden. Result: on first render nothing gets filled in, and only after the *next* periodic poll (once the panel exists and the visibility toggle can find and unhide it) does it get one more chance — which looked like the tab disappearing and reappearing. Fix: `renderSystemStatus()` now always populates its content regardless of the panel's current visibility, matching how every other dynamically-created admin panel (Groups, Retention, API Access) already behaves. Also added the missing spacing between the System tab's stacked `.dashboard-panel` cards (`[data-admin-panel="system"]>.dashboard-panel+.dashboard-panel{margin-top:18px}`) — the generic `.settings-panel` wrapper never had a gap rule for its children, so the cards were rendering edge-to-edge.
|
||||
|
||||
`v0.16.4` rewords the Reload & Restart section's "Docker socket not detected" message so it no longer reads as a duplicate of the Docker container-selection message elsewhere on the System tab. Both checks are independent (one gates the proxy-target container picker, the other gates whether Site Gateway can confirm this container will actually come back up before offering a restart), but they previously used the exact same sentence, which looked like a copy-paste mistake. No behavior change — the Restart button is still disabled under the same conditions as before.
|
||||
|
||||
`v0.16.5` gives the Docker container-selection status its own `.health-tile` card (matching `BACKUP_PASSWORD` directly above it) instead of a plain paragraph — the two Environment/Integrations rows now look consistent whether Docker's socket is mounted or not. Also adds breathing room between the Reload & Restart buttons and the status line beneath them, which was sitting flush against the button row.
|
||||
|
||||
`v0.16.6` fixes the real cause of the System tab's Scheduled jobs section always showing "No scheduled jobs reported.": the dashboard API nests job data under `dashboard.system.jobs`, but the System tab was reading `dashboard.jobs` — one level too shallow, so it was always undefined regardless of what the server returned. Also fixes the Docker socket status tile rendering wider than the `BACKUP_PASSWORD` tile above it — it wasn't wrapped in the same `.health-grid` container, so it spanned the full panel width instead of matching the two-column tile layout used everywhere else on the System tab.
|
||||
|
||||
`v0.16.7` fixes the real cause of the System tab visibly blinking in and out on every page reload: unlike every other Administration tab (Users, Groups, Gateway defaults, Audit log, Backup & restore, Logs & Retention, Danger Zone — all static HTML present from the first paint), System and API Access were built entirely by JavaScript after the initial data fetch completed, so there was a real window on every reload where every other tab was already visible and these two genuinely were not there yet. Confirmed via a screenshot taken mid-reload showing exactly that. Fix: gave System and API Access the same static tab-button-and-panel shell every other tab already has, so they're present immediately; their content still fills in a moment later via JavaScript, same as every other tab already does. Also reworked the Scheduled Jobs section to match the Storage section's card styling (health-tile grid instead of a plain list) and added last-run timestamps where the server tracks them (scheduled backups, log pruning, public IP checks, configuration drift checks).
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "site-gateway",
|
||||
"version": "0.16.3",
|
||||
"version": "0.16.7",
|
||||
"private": true,
|
||||
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
|
||||
"type": "module",
|
||||
|
||||
@@ -453,7 +453,7 @@ function renderDockerPanel() {
|
||||
if (!section) {
|
||||
section = document.createElement("div");
|
||||
section.className = "docker-integration-section";
|
||||
section.innerHTML = '<p class="docker-integration-heading">Docker container selection</p><p class="muted" id="docker-integration-help"></p><label class="check-control"><input id="docker-integration-toggle" type="checkbox"><span>Let Proxy and Streaming hosts pick a running container as their target</span></label>';
|
||||
section.innerHTML = '<p class="docker-integration-heading">Docker container selection</p><div class="health-grid"><div class="health-tile" id="docker-integration-status"><span class="status-dot"></span><span class="health-tile-copy"><strong>Docker socket</strong><small id="docker-integration-help"></small></span></div></div><label class="check-control"><input id="docker-integration-toggle" type="checkbox"><span>Let Proxy and Streaming hosts pick a running container as their target</span></label>';
|
||||
panel.append(section);
|
||||
section.querySelector("#docker-integration-toggle").addEventListener("change", async event => {
|
||||
const checkbox = event.currentTarget;
|
||||
@@ -466,6 +466,7 @@ function renderDockerPanel() {
|
||||
const toggle = section.querySelector("#docker-integration-toggle");
|
||||
toggle.checked = enabled;
|
||||
toggle.disabled = !socketMounted;
|
||||
section.querySelector("#docker-integration-status .status-dot").className = `status-dot ${socketMounted ? "running" : "idle"}`;
|
||||
section.querySelector("#docker-integration-help").textContent = socketMounted
|
||||
? "Site Gateway reads the Docker socket read-only to list running containers, and only offers containers that share a Docker network with it."
|
||||
: "Docker socket not detected — mount /var/run/docker.sock into this container to enable container selection.";
|
||||
@@ -540,7 +541,7 @@ function renderSystemPanel() {
|
||||
'<div class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Environment</p><h2>Integrations</h2></div></div><div id="system-env-status" class="health-grid"></div><div class="system-integrations"></div></div>',
|
||||
'<div class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Environment</p><h2>Security status</h2></div></div><div id="system-security" class="health-grid"></div></div>',
|
||||
'<div class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Gateway</p><h2>Sync</h2></div><button type="button" id="system-resync" class="button secondary">Resync now</button></div><p id="system-sync-status" class="muted"></p></div>',
|
||||
'<div class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Operations</p><h2>Scheduled jobs</h2></div></div><div id="system-jobs" class="dashboard-jobs-list"></div></div>',
|
||||
'<div class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Operations</p><h2>Scheduled jobs</h2></div></div><div id="system-jobs" class="health-grid"></div></div>',
|
||||
'<div class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Storage</p><h2>Disk usage</h2></div></div><div id="system-storage" class="health-grid"></div></div>',
|
||||
'<div class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Build</p><h2>Version</h2></div></div><div id="system-version" class="muted"></div></div>',
|
||||
'<div class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Gateway</p><h2>Reload & restart</h2></div></div><p class="muted">Reloading re-applies the current configuration to Caddy with no downtime. Restarting stops and restarts the whole application \u2014 only available when a restart policy is set on the container.</p><div class="row-actions"><button type="button" id="system-reload" class="button secondary">Reload gateway config</button><button type="button" id="system-restart" class="button secondary danger-text" disabled>Restart application</button></div><p id="system-restart-status" class="muted"></p></div>',
|
||||
@@ -573,7 +574,11 @@ async function renderSystemStatus(panel) {
|
||||
version = document.querySelector("#system-version"), jobs = document.querySelector("#system-jobs"),
|
||||
syncStatus = document.querySelector("#system-sync-status"), restartButton = document.querySelector("#system-restart"),
|
||||
restartStatus = document.querySelector("#system-restart-status");
|
||||
if (jobs) jobs.innerHTML = (state.dashboard?.jobs || []).map(job => `<div class="dashboard-list-item"><span class="status-dot ${job.enabled ? "running" : "idle"}"></span><span><strong>${extendedEscape(job.name)}</strong><small>${job.enabled ? `Active \u00b7 ${extendedEscape(job.schedule)}` : "Disabled"}</small></span></div>`).join("") || '<p class="quiet-state">No scheduled jobs reported.</p>';
|
||||
if (jobs) jobs.innerHTML = (state.dashboard?.system?.jobs || []).map(job => {
|
||||
const status = job.enabled ? `Active \u00b7 ${extendedEscape(job.schedule)}` : "Disabled";
|
||||
const lastRun = job.lastRunAt ? `Last run ${extendedEscape(formatTime(job.lastRunAt))}${job.lastStatus && job.lastStatus !== "ok" ? ` \u00b7 ${extendedEscape(job.lastStatus)}` : ""}` : "No run recorded yet";
|
||||
return `<div class="health-tile"><span class="status-dot ${job.enabled ? "running" : "idle"}"></span><span class="health-tile-copy"><strong>${extendedEscape(job.name)}</strong><small>${status} \u00b7 ${lastRun}</small></span></div>`;
|
||||
}).join("") || '<p class="quiet-state">No scheduled jobs reported.</p>';
|
||||
const envStatus = document.querySelector("#system-env-status");
|
||||
if (envStatus) {
|
||||
const encryptionAvailable = Boolean(state.config?.backup?.encryptionAvailable);
|
||||
|
||||
@@ -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.3">
|
||||
<link rel="stylesheet" href="/styles.css?v=0.16.7">
|
||||
</head>
|
||||
|
||||
<!-- ================================================================
|
||||
@@ -208,9 +208,9 @@
|
||||
Audit log, and Logs & Retention, is inserted dynamically by features.js's
|
||||
renderSystemPanel(). -->
|
||||
<section id="users-view" class="feature-view hidden">
|
||||
<div class="admin-tabs"><button class="tab-active" data-admin-tab="users">Users</button><button data-admin-tab="groups">Groups</button><button data-admin-tab="defaults">Gateway defaults</button><button data-admin-tab="audit">Audit log</button><button data-admin-tab="backups">Backup & restore</button><button data-admin-tab="retention">Logs & Retention</button><button data-admin-tab="danger" class="danger-tab">Danger Zone</button></div>
|
||||
<div class="admin-tabs"><button data-admin-tab="system">System</button><button class="tab-active" data-admin-tab="users">Users</button><button data-admin-tab="groups">Groups</button><button data-admin-tab="defaults">Gateway defaults</button><button data-admin-tab="audit">Audit log</button><button data-admin-tab="backups">Backup & restore</button><button data-admin-tab="retention">Logs & Retention</button><button data-admin-tab="api">API Access</button><button data-admin-tab="danger" class="danger-tab">Danger Zone</button></div>
|
||||
<!-- Users tab -->
|
||||
<section data-admin-panel="users">
|
||||
<section data-admin-panel="system" class="hidden settings-panel"></section><section data-admin-panel="api" class="hidden settings-panel"></section><section data-admin-panel="users">
|
||||
<div id="user-summary" class="summary user-summary" aria-label="User summary"></div>
|
||||
<div id="user-list" class="user-grid"><p class="quiet-state">Loading users…</p></div>
|
||||
</section>
|
||||
@@ -439,6 +439,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.3" defer></script><script src="/features.js?v=0.16.3" defer></script><script src="/select-enhance.js?v=0.16.3" defer></script>
|
||||
<script src="/app.js?v=0.16.7" defer></script><script src="/features.js?v=0.16.7" defer></script><script src="/select-enhance.js?v=0.16.7" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -775,7 +775,9 @@ label.check-control:has(input[name="upstreamTlsInsecure"]) small{position:absolu
|
||||
|
||||
.docker-integration-section{margin-top:14px;padding-top:14px;border-top:1px solid var(--line)}
|
||||
.docker-integration-heading{font-weight:600;margin:0 0 var(--space-2)}
|
||||
.docker-integration-section .health-tile{margin-bottom:var(--space-3)}
|
||||
.docker-integration-section .check-control{margin-top:var(--space-3)}
|
||||
#system-restart-status{margin-top:var(--space-3)}
|
||||
|
||||
/* Custom-drawn select (Task #20): native <select> popups can't be reliably themed dark across
|
||||
browsers, so this replaces the popup only -- the native select stays for value/form/event
|
||||
|
||||
+9
-2
@@ -1013,7 +1013,14 @@ async function dashboardSnapshot() {
|
||||
publicIp: publicIpState.address,
|
||||
publicIpCheckedAt: publicIpState.checkedAt,
|
||||
publicIpError: publicIpState.error,
|
||||
jobs: [{ name: "Upstream checks", enabled: true, schedule: "60s" }, { name: "Scheduled backups", enabled: Boolean(settings.backups?.enabled), schedule: settings.backups?.enabled ? settings.backups.frequency : "off" }, { name: "Log pruning", enabled: Boolean(settings.logsRetention?.pruningEnabled), schedule: settings.logsRetention?.pruningEnabled ? "15m" : "off" }, { name: "Access-log import", enabled: true, schedule: "30s" }, { name: "Public IP check", enabled: true, schedule: "60m" }, { name: "Configuration drift check", enabled: true, schedule: "10m" }]
|
||||
jobs: [
|
||||
{ name: "Upstream checks", enabled: true, schedule: "60s", lastRunAt: null },
|
||||
{ name: "Scheduled backups", enabled: Boolean(settings.backups?.enabled), schedule: settings.backups?.enabled ? settings.backups.frequency : "off", lastRunAt: settings.backups?.lastRunAt || null, lastStatus: settings.backups?.lastStatus || null },
|
||||
{ name: "Log pruning", enabled: Boolean(settings.logsRetention?.pruningEnabled), schedule: settings.logsRetention?.pruningEnabled ? "15m" : "off", lastRunAt: settings.logsRetention?.lastRunAt || null },
|
||||
{ name: "Access-log import", enabled: true, schedule: "30s", lastRunAt: null },
|
||||
{ name: "Public IP check", enabled: true, schedule: "60m", lastRunAt: publicIpState.checkedAt || null },
|
||||
{ name: "Configuration drift check", enabled: true, schedule: "10m", lastRunAt: configDrift.checkedAt || null },
|
||||
]
|
||||
},
|
||||
activity: recentActivity
|
||||
};
|
||||
@@ -1590,7 +1597,7 @@ app.get("/api/system/storage", async (req, res, next) => {
|
||||
// Inspects this container's own restart policy via the Docker Engine API, reusing the same
|
||||
// mounted-socket + self-identification (process.env.HOSTNAME) pattern as the container picker.
|
||||
async function ownRestartPolicy() {
|
||||
if (!dockerSocketMounted) return { checked: false, policyName: null, restartAvailable: false, reason: "Docker socket not detected — mount /var/run/docker.sock to check the restart policy." };
|
||||
if (!dockerSocketMounted) return { checked: false, policyName: null, restartAvailable: false, reason: "Restart availability can’t be verified — mount /var/run/docker.sock into this container so Site Gateway can confirm it will come back up before offering a restart." };
|
||||
const ownId = String(process.env.HOSTNAME || "").trim();
|
||||
if (!ownId) return { checked: false, policyName: null, restartAvailable: false, reason: "Could not determine this container's own ID." };
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user