Compare commits

...

3 Commits

6 changed files with 24 additions and 18 deletions
+7 -1
View File
@@ -243,4 +243,10 @@ Roughly in priority order:
`v0.16.59` is a release-readiness cleanup pass, prompted by an upcoming public release: a genuine CSS bug, and several stale claims in the in-app Documentation manual that had drifted from what the app actually does after the Certificates/Logs/Runtime work in v0.16.51 through v0.16.58. Fixed: the Documentation page's intro paragraph was rendering in the bright body-text color instead of muted gray, because the CSS rule targeting it used `p:last-child`, which stopped matching once the search box `<div>` was added after it as the real last child -- it now has its own dedicated class instead of relying on element position. In the manual itself: the Certificates overview article said Upstream health only applied to Proxy Hosts (true before v0.16.52, not since); the Certificates field-reference article said certificate detail came from "expanding" a row and that thresholds were set from a nonexistent "Administration -> Security & Health" location (they're on the Certificates page itself, both predating and unrelated to this session's changes); the Dashboard and System articles' Scheduled Jobs lists were missing the database-integrity and disk-usage-refresh jobs added in v0.16.56; the System article still claimed the Disk usage breakdown showed total/remaining capacity, which moved to the Runtime panel's Disk stat in v0.16.57; and neither the Dashboard nor System Runtime sections mentioned the value-text threshold coloring added in v0.16.57. The Logs article also picked up one clarifying sentence noting Gateway Events now has the same pinned column-header behavior as Access Logs. Outside the app: the README version badge and the ZimaOS App Store manifest's `version`/`update_at` fields were several releases stale (0.16.50 and 0.11.101 respectively) and are now current. `v0.16.59` is a release-readiness cleanup pass, prompted by an upcoming public release: a genuine CSS bug, and several stale claims in the in-app Documentation manual that had drifted from what the app actually does after the Certificates/Logs/Runtime work in v0.16.51 through v0.16.58. Fixed: the Documentation page's intro paragraph was rendering in the bright body-text color instead of muted gray, because the CSS rule targeting it used `p:last-child`, which stopped matching once the search box `<div>` was added after it as the real last child -- it now has its own dedicated class instead of relying on element position. In the manual itself: the Certificates overview article said Upstream health only applied to Proxy Hosts (true before v0.16.52, not since); the Certificates field-reference article said certificate detail came from "expanding" a row and that thresholds were set from a nonexistent "Administration -> Security & Health" location (they're on the Certificates page itself, both predating and unrelated to this session's changes); the Dashboard and System articles' Scheduled Jobs lists were missing the database-integrity and disk-usage-refresh jobs added in v0.16.56; the System article still claimed the Disk usage breakdown showed total/remaining capacity, which moved to the Runtime panel's Disk stat in v0.16.57; and neither the Dashboard nor System Runtime sections mentioned the value-text threshold coloring added in v0.16.57. The Logs article also picked up one clarifying sentence noting Gateway Events now has the same pinned column-header behavior as Access Logs. Outside the app: the README version badge and the ZimaOS App Store manifest's `version`/`update_at` fields were several releases stale (0.16.50 and 0.11.101 respectively) and are now current.
`v0.16.60` fixes the Documentation manual's intro header, which sat as a full-width bordered panel with no cap on its content width -- on wide screens the eyebrow, heading, and lede paragraph (already capped at 850px) left a large block of empty space to their right inside the panel, since nothing constrained the panel itself. The intro block now has its own max-width (900px), centered within the page the same way the rest of the manual's reading column is, so the header reads as one balanced block instead of left-aligned text trailing off into open space. `v0.16.60` fixes the Documentation manual's intro header leaving a large block of empty space on wide screens, by capping the whole `.docs-intro` panel at 900px and centering it. Shipped, then reported back as visibly worse: centering the panel broke its alignment with the Contents sidebar and article panel in `.docs-layout` directly below it, which both remain full-width -- the intro now floats disconnected in the middle of the page instead of lining up with the rest of the manual.
`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", "name": "site-gateway",
"version": "0.16.60", "version": "0.16.63",
"private": true, "private": true,
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.", "description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
"type": "module", "type": "module",
+1 -1
View File
@@ -181,7 +181,7 @@ function renderDashboard() {
$("#dash-hosted-detail").textContent = healthCopy(data.hosted, "sites"); $("#dash-hosted-detail").textContent = healthCopy(data.hosted, "sites");
$("#dash-proxy-total").textContent = data.proxies.total; $("#dash-proxy-total").textContent = data.proxies.total;
$("#dash-proxy-detail").textContent = healthCopy(data.proxies, "routes"); $("#dash-proxy-detail").textContent = healthCopy(data.proxies, "routes");
$("#dash-tls-total").textContent = data.tlsDomains; $("#dash-tls-total").textContent = data.certificates.total;
$("#dash-tls-detail").textContent = data.certificates.total ? `${data.certificates.healthy} healthy · ${data.certificates.pending} not detected` : "No TLS domains"; $("#dash-tls-detail").textContent = data.certificates.total ? `${data.certificates.healthy} healthy · ${data.certificates.pending} not detected` : "No TLS domains";
$("#dash-redirect-total").textContent = state.redirects?.length || 0; $("#dash-redirect-total").textContent = state.redirects?.length || 0;
$("#dash-stream-total").textContent = state.streams?.length || 0; $("#dash-stream-total").textContent = state.streams?.length || 0;
+10 -4
View File
@@ -470,13 +470,19 @@ function backupHistoryLabel(event) {
if (event.type === "imported") return `Imported ${lower}${when}`; if (event.type === "imported") return `Imported ${lower}${when}`;
return `${kind}${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() { async function renderBackupHistory() {
const panel = document.querySelector('[data-admin-panel="backups"]'); if (!panel || state.user?.role !== "administrator") return; const panel = document.querySelector('[data-admin-panel="backups"]'); if (!panel || state.user?.role !== "administrator") return;
let section = panel.querySelector(".backup-history-section"); let section = panel.querySelector(".backup-history-section");
if (!section) { if (!section) {
section = document.createElement("div"); section = document.createElement("div");
section.className = "dashboard-panel backup-history-section"; 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); panel.append(section);
} }
const list = section.querySelector("#backup-history-list"); const list = section.querySelector("#backup-history-list");
@@ -485,9 +491,9 @@ async function renderBackupHistory() {
list.innerHTML = events.length ? events.map(item => { list.innerHTML = events.length ? events.map(item => {
const failed = item.status === "failed"; 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"; 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>`; 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("") : '<p class="quiet-state">No backup activity recorded yet.</p>'; }).join("") : '<tr><td colspan="4" class="quiet-state">No backup activity recorded yet.</td></tr>';
} catch (error) { list.innerHTML = `<p class="quiet-state">${extendedEscape(error.message)}</p>`; } } catch (error) { list.innerHTML = `<tr><td colspan="4" class="quiet-state">${extendedEscape(error.message)}</td></tr>`; }
} }
+3 -3
View File
@@ -8,7 +8,7 @@
<title>Site Gateway</title> <title>Site Gateway</title>
<meta name="description" content="Host sites, proxy services, and manage HTTPS from one simple dashboard."> <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="icon" type="image/png" href="/site-gateway-icon-approved.png">
<link rel="stylesheet" href="/styles.css?v=0.16.60"> <link rel="stylesheet" href="/styles.css?v=0.16.63">
</head> </head>
<!-- ================================================================ <!-- ================================================================
@@ -109,7 +109,7 @@
<div class="metric-grid"> <div class="metric-grid">
<button class="metric-card accent-green" data-target="hosted"><span class="metric-icon"></span><span class="metric-label">Hosted sites</span><strong id="dash-hosted-total">0</strong><span id="dash-hosted-detail">None configured</span></button> <button class="metric-card accent-green" data-target="hosted"><span class="metric-icon"></span><span class="metric-label">Hosted sites</span><strong id="dash-hosted-total">0</strong><span id="dash-hosted-detail">None configured</span></button>
<button class="metric-card accent-green" data-target="proxies"><span class="metric-icon"></span><span class="metric-label">Proxy hosts</span><strong id="dash-proxy-total">0</strong><span id="dash-proxy-detail">None configured</span></button> <button class="metric-card accent-green" data-target="proxies"><span class="metric-icon"></span><span class="metric-label">Proxy hosts</span><strong id="dash-proxy-total">0</strong><span id="dash-proxy-detail">None configured</span></button>
<button class="metric-card accent-green" data-target="certificates"><span class="metric-icon"></span><span class="metric-label">Certificates</span><strong id="dash-tls-total">0</strong><span id="dash-tls-detail">No TLS domains</span></button> <button class="metric-card accent-green" data-target="certificates"><span class="metric-icon"><svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="8.5" cy="8.5" r="3.4"></circle><path d="M10.9 10.9 17.8 17.8"></path><path d="M15.2 15.2 17 17"></path><path d="M17.4 17.4 19.5 15.3"></path></svg></span><span class="metric-label">Certificates</span><strong id="dash-tls-total">0</strong><span id="dash-tls-detail">No TLS domains</span></button>
</div> </div>
<div class="metric-strip"> <div class="metric-strip">
<button class="metric-chip accent-green" data-target="redirects"><span class="chip-icon"></span><span class="chip-copy"><span class="metric-label">Redirect hosts</span><strong id="dash-redirect-total">0</strong></span></button> <button class="metric-chip accent-green" data-target="redirects"><span class="chip-icon"></span><span class="chip-copy"><span class="metric-label">Redirect hosts</span><strong id="dash-redirect-total">0</strong></span></button>
@@ -451,6 +451,6 @@
<div id="toast" class="toast" role="status"></div> <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> <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) --> <!-- App scripts: core (app.js) then extended views/admin (features.js) -->
<script src="/app.js?v=0.16.60" defer></script><script src="/features.js?v=0.16.60" defer></script><script src="/select-enhance.js?v=0.16.60" 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> </body>
</html> </html>
+2 -8
View File
@@ -688,7 +688,7 @@ dialog{max-height:calc(100vh - 28px);overflow:auto}
/* Documentation manual: intro & search */ /* Documentation manual: intro & search */
.docs-intro{margin-bottom:22px;padding:var(--space-5);border:1px solid var(--line);border-radius:var(--radius-lg);background:var(--bg);color:var(--text)} .docs-intro{margin-bottom:22px;padding:var(--space-5);border:1px solid var(--line);border-radius:var(--radius-lg);background:var(--bg);color:var(--text)}
.docs-intro h2{margin:0 0 10px} .docs-intro h2{margin:0 0 10px}
.docs-intro .docs-lede{margin:0;color:var(--muted);max-width:850px;line-height:1.6} .docs-intro .docs-lede{margin:0;color:var(--muted);line-height:1.6}
.docs-layout{display:grid;grid-template-columns:210px minmax(0,1fr);gap:22px;align-items:start} .docs-layout{display:grid;grid-template-columns:210px minmax(0,1fr);gap:22px;align-items:start}
.docs-nav{position:sticky;top:18px;height:calc(100vh - 170px);overflow:auto;display:grid;align-content:start;gap:6px;padding:14px;border:1px solid var(--line);border-radius:var(--radius-2xl);background:var(--panel)} .docs-nav{position:sticky;top:18px;height:calc(100vh - 170px);overflow:auto;display:grid;align-content:start;gap:6px;padding:14px;border:1px solid var(--line);border-radius:var(--radius-2xl);background:var(--panel)}
.docs-nav .eyebrow{margin:var(--space-1) 8px 8px} .docs-nav .eyebrow{margin:var(--space-1) 8px 8px}
@@ -719,7 +719,7 @@ dialog{max-height:calc(100vh - 28px);overflow:auto}
.docs{display:block} .docs{display:block}
.docs-intro{grid-column:1/-1} .docs-intro{grid-column:1/-1}
.docs-layout{width:100%} .docs-layout{width:100%}
.docs-intro{text-align:left;max-width:900px;margin-left:auto;margin-right:auto} .docs-intro{text-align:left}
.docs-intro>p:not(.eyebrow){margin-left:0;margin-right:0} .docs-intro>p:not(.eyebrow){margin-left:0;margin-right:0}
.docs-search-panel{margin-left:0;margin-right:0} .docs-search-panel{margin-left:0;margin-right:0}
@@ -994,12 +994,6 @@ select{appearance:none!important;-webkit-appearance:none!important;background-re
.api-token-hide-revoked span{line-height:normal} .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} .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{opacity:.55}
.check-control.is-disabled span{color:var(--muted)} .check-control.is-disabled span{color:var(--muted)}
.docker-integration-section,.backup-history-section{margin-top:var(--space-5)} .docker-integration-section,.backup-history-section{margin-top:var(--space-5)}