From d5e2375f3866df06aebf44713b9af23a832eb3a4 Mon Sep 17 00:00:00 2001 From: librelad Date: Mon, 1 Jun 2026 00:37:59 +0100 Subject: [PATCH] fix(webui): address review findings on the fleet Overview build - HIGH: renderAppDetail gated the Roll back button on last_snapshot* fields no generator emits, so it never rendered. Derive recoverability from update history (updater_apply always snapshots first) so the affordance is reachable. - MED: per-app Updates tab now repaints on update/rollback/check/hotfix task completion (mirrors the backups card) instead of going stale until re-click. - MED: in-page tab switches now sync spaClean.currentRoute, so the sidebar Overview entry no longer no-ops after switching tabs. - LOW: keyboard activation (Enter/Space) for the role=button expander heads, backup tiles, and sidebar Overview entry. - LOW: preserve ALL expanded Updates rows across a background repaint, not just the single ?app= deep-link (split toggle into _openDetail/_closeDetail). Co-Authored-By: Claude Opus 4.8 --- .../apps/core/html/apps-unified-layout.html | 3 +- .../apps/core/js/app-tabbed-manager.js | 7 ++ .../apps/overview/js/overview-manager.js | 88 ++++++++++++++----- .../components/updater/js/updater-page.js | 15 +++- 4 files changed, 89 insertions(+), 24 deletions(-) diff --git a/containers/libreportal/frontend/components/apps/core/html/apps-unified-layout.html b/containers/libreportal/frontend/components/apps/core/html/apps-unified-layout.html index 61cfeaa..91832f3 100755 --- a/containers/libreportal/frontend/components/apps/core/html/apps-unified-layout.html +++ b/containers/libreportal/frontend/components/apps/core/html/apps-unified-layout.html @@ -9,7 +9,8 @@
+ onclick="if(window.navigateToRoute){window.navigateToRoute('/overview');}else{window.location.href='/overview';}" + onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();this.click();}">

Security

${ cves.length ? cveItems : '

No known CVEs. 🎉

'}
`; - const can = !!a.last_snapshot; - const snap = can + // A rollback target exists if a snapshot field is present (future-proofing) + // OR — the data the generator actually emits today — this app has a prior + // update/rollback in history (updater_apply always snapshots first, so that + // pre-update snapshot is the rollback point). Gating only on the never- + // written last_snapshot* fields would make the Roll back button unreachable. + const priorUpdate = ((this.history && this.history.entries) || []) + .some((e) => e.app === a.name && (e.action === 'update' || e.action === 'rollback')); + const can = !!a.last_snapshot || priorUpdate; + const snap = a.last_snapshot ? `${this.escape(a.last_snapshot_version || '')} · ${this.fmtRel(a.last_snapshot_at)}` - : 'A recovery snapshot is taken automatically before the next update.'; + : (priorUpdate + ? 'Roll back to the snapshot taken before the last update.' + : 'A recovery snapshot is taken automatically before the next update.'); const recovery = `

Recovery

${can ? 'recoverable' : 'protected'} ${snap}