Compare commits

...

1 Commits

5 changed files with 15 additions and 13 deletions
+2
View File
@@ -248,3 +248,5 @@ Roughly in priority order:
`v0.16.61` reverts v0.16.60's panel-centering approach and fixes the same empty-space issue correctly. The actual cause was the lede paragraph's own `max-width:850px`, set inside a `.docs-intro` panel that already spans the full reading-column width (matching the Contents sidebar + article layout beneath it) -- the paragraph simply stopped well short of the panel's real width. Removing that max-width lets the paragraph flow to fill the panel it already sits in, while the panel itself stays full-width and aligned with the rest of the manual, exactly as it was before v0.16.60.
`v0.16.62` fixes two Dashboard issues found while reviewing a fresh screenshot for the marketing site. First, the Certificates tile's big number and its "X healthy · Y not detected" line disagreed with each other (e.g. showing "11" above "12 healthy") because they came from two different counts: the big number used `tlsDomains`, a route-count over enabled Hosted Sites and Proxy Hosts with TLS on, while the detail line used the certificate inventory's domain-level summary -- which also includes Redirect Hosts and expands any route with alias domains into one entry per domain. The big number now reads from that same certificate summary the detail line already used, so a route with an alias domain (the actual cause of the 11-vs-12 mismatch) is counted consistently in both places; the underlying route-count (`tlsDomains`) is untouched where it's used to gate the HTTPS/443 health probe, since that is a genuinely different question ("is TLS configured at all") from "how many certificates exist." Second, the Certificates tile's icon was a plain unicode square (`▣`) left over from before the tile had a real icon design -- it's now a small key glyph drawn as an inline SVG in the app's existing icon style (stroke-based, `currentColor`), matching the key icon used for Certificates on the marketing website and requested directly against a reference image.
`v0.16.63` converts the Backup history panel (Administration -> Backup & Restore) from a stacked-card timeline to the same pinned-header table used everywhere else records are logged -- Access Logs and Gateway Events. It was the one remaining place in the app presenting a log-like record as a list of `.activity-tile` cards (the same component the Dashboard's Recent Activity feed uses) instead of a table, and looked inconsistent sitting next to those two. The table has four columns -- Time, Action (Backup/Restored/Deleted/Imported), Type (Complete/Configuration/Safety), and Detail (size, a note that a safety backup was taken first, or the failure reason) -- reusing the exact `performance-table`/`event-table` CSS and column widths Gateway Events already established, plus the same green/red `status-dot` convention for success vs. failure. No backend or data changes; `/api/backups/history` already returned everything the new columns needed.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "site-gateway",
"version": "0.16.62",
"version": "0.16.63",
"private": true,
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
"type": "module",
+10 -4
View File
@@ -470,13 +470,19 @@ function backupHistoryLabel(event) {
if (event.type === "imported") return `Imported ${lower}${when}`;
return `${kind}${when}`;
}
function backupHistoryAction(event) {
return event.type === "restored" ? "Restored" : event.type === "deleted" ? "Deleted" : event.type === "imported" ? "Imported" : "Backup";
}
function backupHistoryType(event) {
return event.backupType === "complete" ? "Complete" : event.backupType === "configuration" ? "Configuration" : event.backupType === "safety" ? "Safety (pre-restore)" : "—";
}
async function renderBackupHistory() {
const panel = document.querySelector('[data-admin-panel="backups"]'); if (!panel || state.user?.role !== "administrator") return;
let section = panel.querySelector(".backup-history-section");
if (!section) {
section = document.createElement("div");
section.className = "dashboard-panel backup-history-section";
section.innerHTML = '<div class="panel-heading"><div><p class="eyebrow">History</p><h2>Backup history</h2><p class="muted">Every backup, restore, import, and deletion — including failed attempts — recorded independently of what is currently stored on disk.</p></div></div><div id="backup-history-list" class="backup-history-list"><p class="quiet-state">Loading backup history…</p></div>';
section.innerHTML = '<div class="panel-heading"><div><p class="eyebrow">History</p><h2>Backup history</h2><p class="muted">Every backup, restore, import, and deletion — including failed attempts — recorded independently of what is currently stored on disk.</p></div></div><div class="table-wrap performance-table-wrap event-table-wrap"><table class="performance-table event-table"><thead><tr><th>Time</th><th>Action</th><th>Type</th><th>Detail</th></tr></thead><tbody id="backup-history-list"><tr><td colspan="4" class="quiet-state">Loading backup history…</td></tr></tbody></table></div>';
panel.append(section);
}
const list = section.querySelector("#backup-history-list");
@@ -485,9 +491,9 @@ async function renderBackupHistory() {
list.innerHTML = events.length ? events.map(item => {
const failed = item.status === "failed";
const detail = failed ? `Failed — ${item.errorMessage || "no further detail recorded"}` : [item.sizeBytes ? formatBytes(item.sizeBytes) : "", item.safetyBackupFilename ? "A safety backup was taken first" : ""].filter(Boolean).join(" · ") || "Completed";
return `<div class="activity-tile"><span class="activity-mark ${failed ? "bad" : ""}">${failed ? "!" : "✓"}</span><span class="backup-history-copy"><strong>${extendedEscape(backupHistoryLabel(item))}</strong><small class="${failed ? "failed" : ""}" title="${extendedEscape(formatTime(item.createdAt))}">${extendedEscape(detail)}</small></span></div>`;
}).join("") : '<p class="quiet-state">No backup activity recorded yet.</p>';
} catch (error) { list.innerHTML = `<p class="quiet-state">${extendedEscape(error.message)}</p>`; }
return `<tr><td>${extendedEscape(formatTime(item.createdAt))}</td><td>${extendedEscape(backupHistoryAction(item))}</td><td>${extendedEscape(backupHistoryType(item))}</td><td><span class="status-dot ${failed ? "error" : "running"}"></span>${extendedEscape(detail)}</td></tr>`;
}).join("") : '<tr><td colspan="4" class="quiet-state">No backup activity recorded yet.</td></tr>';
} catch (error) { list.innerHTML = `<tr><td colspan="4" class="quiet-state">${extendedEscape(error.message)}</td></tr>`; }
}
+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.62">
<link rel="stylesheet" href="/styles.css?v=0.16.63">
</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.62" defer></script><script src="/features.js?v=0.16.62" defer></script><script src="/select-enhance.js?v=0.16.62" defer></script>
<script src="/app.js?v=0.16.63" defer></script><script src="/features.js?v=0.16.63" defer></script><script src="/select-enhance.js?v=0.16.63" defer></script>
</body>
</html>
-6
View File
@@ -994,12 +994,6 @@ select{appearance:none!important;-webkit-appearance:none!important;background-re
.api-token-hide-revoked span{line-height:normal}
.api-token-secret{display:block;margin-top:var(--space-3);padding:var(--space-3) var(--space-4);border:1px solid var(--line);border-radius:var(--radius-sm);background:rgba(var(--bg-rgb),.4);color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:var(--font-size-sm);line-height:1.6;word-break:break-all}
/* Backup history timeline */
.backup-history-list{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-4);max-height:min(46vh,520px);overflow:auto}
.backup-history-copy{display:flex;flex-direction:column;gap:2px;min-width:0}
.backup-history-copy strong{font-size:var(--font-size-md)}
.backup-history-copy small{color:var(--muted);font-size:var(--font-size-xs)}
.backup-history-copy small.failed{color:var(--danger)}
.check-control.is-disabled{opacity:.55}
.check-control.is-disabled span{color:var(--muted)}
.docker-integration-section,.backup-history-section{margin-top:var(--space-5)}