From dbc143dfbded2cbbe78688620a42f37270fe9c63 Mon Sep 17 00:00:00 2001 From: Marvin Wade Date: Sun, 20 Sep 2026 13:41:18 -0400 Subject: [PATCH] Convert Backup history to pinned-header table matching Access Logs / Gateway Events --- ROADMAP.md | 2 ++ package.json | 2 +- src/public/features.js | 14 ++++++++++---- src/public/index.html | 4 ++-- src/public/styles.css | 6 ------ 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index cafdf32..f2623cd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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. diff --git a/package.json b/package.json index b65be14..aa669e8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/public/features.js b/src/public/features.js index 59cfb64..2065367 100644 --- a/src/public/features.js +++ b/src/public/features.js @@ -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 = '

History

Backup history

Every backup, restore, import, and deletion — including failed attempts — recorded independently of what is currently stored on disk.

Loading backup history…

'; + section.innerHTML = '

History

Backup history

Every backup, restore, import, and deletion — including failed attempts — recorded independently of what is currently stored on disk.

TimeActionTypeDetail
Loading backup history…
'; 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 `
${failed ? "!" : "✓"}${extendedEscape(backupHistoryLabel(item))}${extendedEscape(detail)}
`; - }).join("") : '

No backup activity recorded yet.

'; - } catch (error) { list.innerHTML = `

${extendedEscape(error.message)}

`; } + return `${extendedEscape(formatTime(item.createdAt))}${extendedEscape(backupHistoryAction(item))}${extendedEscape(backupHistoryType(item))}${extendedEscape(detail)}`; + }).join("") : 'No backup activity recorded yet.'; + } catch (error) { list.innerHTML = `${extendedEscape(error.message)}`; } } diff --git a/src/public/index.html b/src/public/index.html index def66d3..366f0ce 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -8,7 +8,7 @@ Site Gateway - + - + diff --git a/src/public/styles.css b/src/public/styles.css index 7007ed6..42d5355 100644 --- a/src/public/styles.css +++ b/src/public/styles.css @@ -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)}