From aef0c15726b1cb56adcb563e21ad07ad6e77f97b Mon Sep 17 00:00:00 2001 From: librelad Date: Sat, 4 Jul 2026 21:08:03 +0100 Subject: [PATCH] refactor(webui/backups): backup center sub-tabs use canonical tabs-wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The embedded backup center's fragment kept its standalone-page skeleton (.container + .sidebar restyled by a pile of overview.css overrides). .container's fixed viewport height inflated the Backups tab pane to ~100vh, stretching the pane surface far past the content and under the footer buttons, and the restyled strip never matched the app-detail Config sub-tabs. Rebuild backup-content.html on the canonical sub-tab idiom instead — .tabs-wrapper > .tabs-list (emoji tab-buttons) + .tabs-content card, with a .backup-actions footer below the card mirroring .config-actions. The bespoke overview.css restyle block, the nebula special-cases, the embed's id-stripping and BackupPage's dead page-header updater all fall away; the export menu now opens upward from the footer. Co-Authored-By: Claude Fable 5 Signed-off-by: librelad --- .../components/apps/overview/css/overview.css | 112 +---------------- .../apps/overview/js/overview-manager.js | 6 +- .../components/backup/core/css/backup.css | 56 ++++----- .../backup/core/html/backup-content.html | 117 +++++++----------- .../components/backup/core/js/backup-page.js | 60 +-------- .../frontend/themes/nebula/theme.css | 14 +-- 6 files changed, 85 insertions(+), 280 deletions(-) diff --git a/containers/libreportal/frontend/components/apps/overview/css/overview.css b/containers/libreportal/frontend/components/apps/overview/css/overview.css index 2c4dbeb..cd09236 100644 --- a/containers/libreportal/frontend/components/apps/overview/css/overview.css +++ b/containers/libreportal/frontend/components/apps/overview/css/overview.css @@ -221,114 +221,10 @@ .updater-detail-empty { color: var(--text-secondary); margin: 0; } /* ---- Backups tab: embedded backup center -------------------------------- */ -/* The Backups tab mounts the real BackupPage, which supplies its own header, and - its left sidebar is restyled into a horizontal nested tab strip so the whole - thing reads as tabs-within-tabs. */ -#overview-view #ov-pane-backups .backup-layout { display: block; } -#overview-view #ov-pane-backups .backup-layout > .sidebar { - width: auto; - max-width: none; - height: auto; - min-height: 0; - margin: 0; /* no gap — the strip joins the content card below */ - padding: 0; - background: transparent; - border: none; -} -/* Match the per-app Config .tabs-list/.tab-button segmented look (full-width - bar, evenly-flexed items, 2px accent underline on the active one). */ -#overview-view #ov-pane-backups .backup-layout > .sidebar #backup-sidebar-list, -#overview-view #ov-pane-backups .backup-layout > .sidebar .sidebar-section { - display: flex; - width: 100%; - background: var(--hover-bg); - border-bottom: 1px solid var(--border-color); - overflow-x: auto; -} -#overview-view #ov-pane-backups .backup-layout > .sidebar .category { - display: flex; - flex: 1 1 0; - min-width: 0; - align-items: center; - justify-content: center; - gap: 6px; - margin: 0; - padding: 12px 14px; - white-space: nowrap; - border: none; - border-bottom: 2px solid transparent; - border-radius: 0; - background: transparent; - cursor: pointer; - /* Type matches .main-tab-button so the nested strip reads like the outer - overview tab strip, not like the backup page's vertical sidebar. */ - font-size: 12px; - font-weight: 500; - color: var(--text-secondary, #ccc); -} -#overview-view #ov-pane-backups .backup-layout > .sidebar .category:hover { - color: var(--accent); - background: rgba(var(--accent-rgb), 0.1); -} -#overview-view #ov-pane-backups .backup-layout > .sidebar .category.active { - color: var(--accent); - border-bottom-color: var(--accent); - background: rgba(var(--accent-rgb), 0.1); -} -#overview-view #ov-pane-backups .backup-layout > .sidebar .category .category-icon { - width: 16px; - height: 16px; - flex: 0 0 auto; -} -/* Round only the outer top corners to follow the strip's chrome (the global - .tab-button rule does this for app/Migrate sub-tabs; .category needs its own). */ -#overview-view #ov-pane-backups .backup-layout > .sidebar .category:first-child { border-top-left-radius: 12px; } -#overview-view #ov-pane-backups .backup-layout > .sidebar .category:last-child { border-top-right-radius: 12px; } -/* .main is just the layout column now — the visible card surface lives on the - body below (so it stops above the footer buttons), exactly like the app - Config tab where .tabs-content is the card and .config-actions sits outside. */ -#overview-view #ov-pane-backups .backup-layout > .main { - width: 100%; - min-width: 0; - background: transparent; -} -/* The body is the connected .tabs-content card: card surface + rounded bottom - corners, joining the tab strip above so the strip + body read as one unit. */ -#overview-view #ov-pane-backups .backup-page-body { - background: var(--card-bg); - border-radius: 0 0 12px 12px; -} -/* Embedded, the nested strip already names the section — BackupPage's big - page header (icon + h1 + subtitle) is redundant chrome. Hide the title - block and flip the header below the body via flex order, so its action - buttons (Refresh + the per-section primary) become a bottom-LEFT footer - row — the same place the app-detail tabs put theirs (.config-actions). */ -#overview-view #ov-pane-backups .backup-page-section { - display: flex; - flex-direction: column; -} -#overview-view #ov-pane-backups .backup-page-section > .page-header { - order: 2; - border-bottom: none; - border-top: none; - background: transparent; - margin-bottom: 0; - /* Flush-left under the card, matching .config-actions (left padding 0). */ - padding: 16px 22px 8px 0; -} -#overview-view #ov-pane-backups .backup-page-section > .page-header .page-header-icon-slot, -#overview-view #ov-pane-backups .backup-page-section > .page-header .page-header-title { - display: none; -} -#overview-view #ov-pane-backups .backup-page-body { order: 1; } -/* The footer row sits low on the page, so the export dropdown opens upward - (and leftward) to stay on-screen rather than clipping past the viewport. */ -#overview-view #ov-pane-backups .backup-page-section > .page-header .backup-export-menu { - top: auto; - bottom: calc(100% + 6px); - right: auto; - left: 0; -} +/* The Backups tab mounts the real BackupPage. Its fragment now uses the + canonical .tabs-wrapper (.tabs-list strip + .tabs-content card) with a + .backup-actions footer — all styled globally (base.css / backup.css), so + no embed-specific restyling is needed here. */ /* ---- Migrate tab: segmented sub-tabs (per-app Config-tab design) -------- */ /* .tabs-wrapper/.tabs-list/.tabs-content/.tab-button come from base.css; the diff --git a/containers/libreportal/frontend/components/apps/overview/js/overview-manager.js b/containers/libreportal/frontend/components/apps/overview/js/overview-manager.js index 31f9084..f076cab 100644 --- a/containers/libreportal/frontend/components/apps/overview/js/overview-manager.js +++ b/containers/libreportal/frontend/components/apps/overview/js/overview-manager.js @@ -625,11 +625,7 @@ class OverviewManager { this._backupLoading = true; try { await this._ensureBackupAssets(); - let html = await fetch('/components/backup/core/html/backup-content.html', { cache: 'no-store' }).then((r) => r.text()); - // Strip ids that would collide with the apps layout (it also has #sidebar - // and #mobile-overlay). BackupPage selects its own nodes by class, so this - // is safe; it just keeps the document free of duplicate ids. - html = html.replace('id="sidebar"', '').replace('
', ''); + const html = await fetch('/components/backup/core/html/backup-content.html', { cache: 'no-store' }).then((r) => r.text()); // Lead with the shared in-content header (BackupPage supplies only its own // per-section headers inside the layout), so Backups matches every other // fleet tab. It persists across refreshes since revisits don't reset innerHTML. diff --git a/containers/libreportal/frontend/components/backup/core/css/backup.css b/containers/libreportal/frontend/components/backup/core/css/backup.css index aebc91f..738251b 100755 --- a/containers/libreportal/frontend/components/backup/core/css/backup.css +++ b/containers/libreportal/frontend/components/backup/core/css/backup.css @@ -1,37 +1,22 @@ -/* Backup Page — restic-engine UI */ - -.backup-layout { - display: flex; - min-height: calc(100vh - var(--topbar-height, 60px)); -} - -.backup-layout .main { - flex: 1; - min-width: 0; - overflow-y: auto; -} +/* Backup Page — restic-engine UI. + Mounted inside the fleet Overview's Backups tab as a canonical + .tabs-wrapper (strip + .tabs-content card) with a .backup-actions footer + below the card, mirroring the app-detail Config tab's layout. */ .backup-page { color: var(--text-primary); width: 100%; - padding-bottom: 48px; } -/* The whole backup page is one .config-section card containing both the - .page-header and the body. Remove the card's inner padding so the - .page-header sits flush at the top and its border-bottom acts as a - full-width divider; the body gets its own padding. */ -.backup-page-section { - padding: 0; - overflow: hidden; -} - -.backup-page-section > .page-header { - margin-bottom: 0; -} - -.backup-page-body { - padding: 22px; +/* Actions footer under the tab card (Refresh + per-section primary), + bottom-left like the app-detail tabs' .config-actions row. */ +.backup-actions { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; + padding: 20px 20px 20px 0; + background: transparent; } /* Configuration tab embeds /config's renderConfig, which emits its own @@ -103,6 +88,9 @@ .backup-tabpanel { display: none; + /* Content inset matching the canonical .tab-panel (5px 24px), so panels + sit at the same depth inside .tabs-content as the Config tab's. */ + padding: 5px 24px; } .backup-tabpanel.active { @@ -940,15 +928,21 @@ color: var(--text-primary); } -/* Export dropdown in the backup page header (Configuration tab). */ +/* Export dropdown in the backup actions footer (Configuration tab). */ #backup-page-header .page-header-actions { position: relative; + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; } +/* The footer sits low on the page, so the menu opens upward (and leftward) + to stay on-screen rather than clipping past the viewport. */ .backup-export-menu { position: absolute; - top: calc(100% + 6px); - right: 0; + bottom: calc(100% + 6px); + left: 0; z-index: 50; min-width: 210px; padding: 6px; diff --git a/containers/libreportal/frontend/components/backup/core/html/backup-content.html b/containers/libreportal/frontend/components/backup/core/html/backup-content.html index 1aff4c7..358aa12 100644 --- a/containers/libreportal/frontend/components/backup/core/html/backup-content.html +++ b/containers/libreportal/frontend/components/backup/core/html/backup-content.html @@ -1,74 +1,26 @@ -
-
- -