refactor(webui/backups): backup center sub-tabs use canonical tabs-wrapper
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 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
9830382a4a
commit
aef0c15726
@ -221,114 +221,10 @@
|
|||||||
.updater-detail-empty { color: var(--text-secondary); margin: 0; }
|
.updater-detail-empty { color: var(--text-secondary); margin: 0; }
|
||||||
|
|
||||||
/* ---- Backups tab: embedded backup center -------------------------------- */
|
/* ---- Backups tab: embedded backup center -------------------------------- */
|
||||||
/* The Backups tab mounts the real BackupPage, which supplies its own header, and
|
/* The Backups tab mounts the real BackupPage. Its fragment now uses the
|
||||||
its left sidebar is restyled into a horizontal nested tab strip so the whole
|
canonical .tabs-wrapper (.tabs-list strip + .tabs-content card) with a
|
||||||
thing reads as tabs-within-tabs. */
|
.backup-actions footer — all styled globally (base.css / backup.css), so
|
||||||
#overview-view #ov-pane-backups .backup-layout { display: block; }
|
no embed-specific restyling is needed here. */
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Migrate tab: segmented sub-tabs (per-app Config-tab design) -------- */
|
/* ---- Migrate tab: segmented sub-tabs (per-app Config-tab design) -------- */
|
||||||
/* .tabs-wrapper/.tabs-list/.tabs-content/.tab-button come from base.css; the
|
/* .tabs-wrapper/.tabs-list/.tabs-content/.tab-button come from base.css; the
|
||||||
|
|||||||
@ -625,11 +625,7 @@ class OverviewManager {
|
|||||||
this._backupLoading = true;
|
this._backupLoading = true;
|
||||||
try {
|
try {
|
||||||
await this._ensureBackupAssets();
|
await this._ensureBackupAssets();
|
||||||
let html = await fetch('/components/backup/core/html/backup-content.html', { cache: 'no-store' }).then((r) => r.text());
|
const 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('<div class="mobile-overlay" id="mobile-overlay"></div>', '');
|
|
||||||
// Lead with the shared in-content header (BackupPage supplies only its own
|
// Lead with the shared in-content header (BackupPage supplies only its own
|
||||||
// per-section headers inside the layout), so Backups matches every other
|
// per-section headers inside the layout), so Backups matches every other
|
||||||
// fleet tab. It persists across refreshes since revisits don't reset innerHTML.
|
// fleet tab. It persists across refreshes since revisits don't reset innerHTML.
|
||||||
|
|||||||
@ -1,37 +1,22 @@
|
|||||||
/* Backup Page — restic-engine UI */
|
/* Backup Page — restic-engine UI.
|
||||||
|
Mounted inside the fleet Overview's Backups tab as a canonical
|
||||||
.backup-layout {
|
.tabs-wrapper (strip + .tabs-content card) with a .backup-actions footer
|
||||||
display: flex;
|
below the card, mirroring the app-detail Config tab's layout. */
|
||||||
min-height: calc(100vh - var(--topbar-height, 60px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.backup-layout .main {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.backup-page {
|
.backup-page {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 48px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The whole backup page is one .config-section card containing both the
|
/* Actions footer under the tab card (Refresh + per-section primary),
|
||||||
.page-header and the body. Remove the card's inner padding so the
|
bottom-left like the app-detail tabs' .config-actions row. */
|
||||||
.page-header sits flush at the top and its border-bottom acts as a
|
.backup-actions {
|
||||||
full-width divider; the body gets its own padding. */
|
display: flex;
|
||||||
.backup-page-section {
|
gap: 12px;
|
||||||
padding: 0;
|
align-items: center;
|
||||||
overflow: hidden;
|
flex-wrap: wrap;
|
||||||
}
|
padding: 20px 20px 20px 0;
|
||||||
|
background: transparent;
|
||||||
.backup-page-section > .page-header {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.backup-page-body {
|
|
||||||
padding: 22px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Configuration tab embeds /config's renderConfig, which emits its own
|
/* Configuration tab embeds /config's renderConfig, which emits its own
|
||||||
@ -103,6 +88,9 @@
|
|||||||
|
|
||||||
.backup-tabpanel {
|
.backup-tabpanel {
|
||||||
display: none;
|
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 {
|
.backup-tabpanel.active {
|
||||||
@ -940,15 +928,21 @@
|
|||||||
color: var(--text-primary);
|
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 {
|
#backup-page-header .page-header-actions {
|
||||||
position: relative;
|
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 {
|
.backup-export-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(100% + 6px);
|
bottom: calc(100% + 6px);
|
||||||
right: 0;
|
left: 0;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
min-width: 210px;
|
min-width: 210px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
|||||||
@ -1,74 +1,26 @@
|
|||||||
<div class="container backup-layout">
|
<div class="backup-layout">
|
||||||
<div class="mobile-overlay" id="mobile-overlay"></div>
|
<!-- Canonical sub-tab idiom (.tabs-wrapper > .tabs-list + .tabs-content),
|
||||||
|
identical to the per-app Config tab and the Migrate tab, so the backup
|
||||||
<div class="sidebar" id="sidebar">
|
center reads as tabs-within-tabs with no bespoke chrome. -->
|
||||||
<div class="sidebar-section" id="backup-sidebar-list">
|
<div class="tabs-wrapper">
|
||||||
<div class="category active" data-backup-tab="dashboard">
|
<div class="tabs-list">
|
||||||
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<button type="button" class="tab-button active" data-backup-tab="dashboard">
|
||||||
<rect x="3" y="3" width="7" height="9"></rect>
|
<span class="tab-emoji">📊</span><span class="tab-name">Dashboard</span>
|
||||||
<rect x="14" y="3" width="7" height="5"></rect>
|
</button>
|
||||||
<rect x="14" y="12" width="7" height="9"></rect>
|
<button type="button" class="tab-button" data-backup-tab="backups">
|
||||||
<rect x="3" y="16" width="7" height="5"></rect>
|
<span class="tab-emoji">💾</span><span class="tab-name">Backups</span>
|
||||||
</svg>
|
</button>
|
||||||
Dashboard
|
<button type="button" class="tab-button" data-backup-tab="locations">
|
||||||
</div>
|
<span class="tab-emoji">📍</span><span class="tab-name">Locations</span>
|
||||||
<div class="category" data-backup-tab="backups">
|
</button>
|
||||||
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<button type="button" class="tab-button" data-backup-tab="configuration">
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
<span class="tab-emoji">⚙️</span><span class="tab-name">Configuration</span>
|
||||||
<polyline points="17 8 12 3 7 8"></polyline>
|
</button>
|
||||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
||||||
</svg>
|
|
||||||
Backups
|
|
||||||
</div>
|
|
||||||
<div class="category" data-backup-tab="locations">
|
|
||||||
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|
||||||
<circle cx="12" cy="10" r="3"></circle>
|
|
||||||
</svg>
|
|
||||||
Locations
|
|
||||||
</div>
|
|
||||||
<div class="category" data-backup-tab="configuration">
|
|
||||||
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
|
||||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
|
||||||
</svg>
|
|
||||||
Configuration
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main">
|
<div class="tabs-content">
|
||||||
<div class="backup-page" id="backup-page">
|
<div class="backup-page" id="backup-page">
|
||||||
<div class="config-section backup-page-section">
|
<span class="backup-engine-badge" id="backup-engine-badge" hidden>restic</span>
|
||||||
<div class="page-header" id="backup-page-header">
|
|
||||||
<div class="page-header-icon-slot" id="backup-page-header-icon"></div>
|
|
||||||
<div class="page-header-title">
|
|
||||||
<h1 id="backup-section-title">Dashboard</h1>
|
|
||||||
<p id="backup-section-subtitle"></p>
|
|
||||||
</div>
|
|
||||||
<div class="page-header-actions">
|
|
||||||
<button class="backup-refresh-btn" id="backup-refresh-btn" title="Refresh">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<polyline points="23 4 23 10 17 10"></polyline>
|
|
||||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
|
||||||
</svg>
|
|
||||||
Refresh
|
|
||||||
</button>
|
|
||||||
<button class="backup-primary-btn" id="backup-primary-action"></button>
|
|
||||||
<div class="backup-export-menu" id="backup-export-menu" role="menu" hidden>
|
|
||||||
<button type="button" class="backup-export-menu-item" data-action="export-passwords" role="menuitem">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
||||||
<polyline points="7 10 12 15 17 10"></polyline>
|
|
||||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
|
||||||
</svg>
|
|
||||||
Repository Passwords
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="backup-page-body">
|
|
||||||
<span class="backup-engine-badge" id="backup-engine-badge" hidden>restic</span>
|
|
||||||
|
|
||||||
<section class="backup-tabpanel active" id="backup-panel-dashboard">
|
<section class="backup-tabpanel active" id="backup-panel-dashboard">
|
||||||
<div class="backup-summary-row" id="backup-summary-row"></div>
|
<div class="backup-summary-row" id="backup-summary-row"></div>
|
||||||
@ -116,7 +68,32 @@
|
|||||||
<section class="backup-tabpanel" id="backup-panel-configuration">
|
<section class="backup-tabpanel" id="backup-panel-configuration">
|
||||||
<div id="backup-configuration-body"></div>
|
<div id="backup-configuration-body"></div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions footer: sits below the tab card at bottom-left, exactly where
|
||||||
|
the app-detail tabs put .config-actions. The per-section primary button
|
||||||
|
label is driven by updatePrimaryAction(). -->
|
||||||
|
<div class="backup-actions" id="backup-page-header">
|
||||||
|
<div class="page-header-actions">
|
||||||
|
<button class="backup-refresh-btn" id="backup-refresh-btn" title="Refresh">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<polyline points="23 4 23 10 17 10"></polyline>
|
||||||
|
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
||||||
|
</svg>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
<button class="backup-primary-btn" id="backup-primary-action"></button>
|
||||||
|
<div class="backup-export-menu" id="backup-export-menu" role="menu" hidden>
|
||||||
|
<button type="button" class="backup-export-menu-item" data-action="export-passwords" role="menuitem">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||||
|
<polyline points="7 10 12 15 17 10"></polyline>
|
||||||
|
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||||
|
</svg>
|
||||||
|
Repository Passwords
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -29,7 +29,6 @@ class BackupPage {
|
|||||||
await this.refreshAll();
|
await this.refreshAll();
|
||||||
await window.Dismissible?.load();
|
await window.Dismissible?.load();
|
||||||
this.render();
|
this.render();
|
||||||
this.updatePageHeader();
|
|
||||||
this.updatePrimaryAction();
|
this.updatePrimaryAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,11 +52,11 @@ class BackupPage {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toggle the sidebar .active class + panel visibility without going
|
/* Toggle the tab strip's .active class + panel visibility without going
|
||||||
through switchTab's URL-update path (used on initial render and
|
through switchTab's URL-update path (used on initial render and
|
||||||
browser back/forward). */
|
browser back/forward). */
|
||||||
applyActiveTabUi(tab) {
|
applyActiveTabUi(tab) {
|
||||||
document.querySelectorAll('.backup-layout .sidebar .category[data-backup-tab]').forEach(b => {
|
document.querySelectorAll('.backup-layout .tabs-list .tab-button[data-backup-tab]').forEach(b => {
|
||||||
b.classList.toggle('active', b.dataset.backupTab === tab);
|
b.classList.toggle('active', b.dataset.backupTab === tab);
|
||||||
});
|
});
|
||||||
document.querySelectorAll('.backup-tabpanel').forEach(p => {
|
document.querySelectorAll('.backup-tabpanel').forEach(p => {
|
||||||
@ -104,7 +103,7 @@ class BackupPage {
|
|||||||
this.toggleExportMenu(false);
|
this.toggleExportMenu(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabBtn = e.target.closest('.backup-layout .sidebar .category[data-backup-tab]');
|
const tabBtn = e.target.closest('.backup-layout .tabs-list .tab-button[data-backup-tab]');
|
||||||
if (tabBtn) {
|
if (tabBtn) {
|
||||||
this.switchTab(tabBtn.dataset.backupTab);
|
this.switchTab(tabBtn.dataset.backupTab);
|
||||||
return;
|
return;
|
||||||
@ -339,7 +338,6 @@ class BackupPage {
|
|||||||
if (!tab || tab === this.currentTab) return;
|
if (!tab || tab === this.currentTab) return;
|
||||||
this.currentTab = tab;
|
this.currentTab = tab;
|
||||||
this.applyActiveTabUi(tab);
|
this.applyActiveTabUi(tab);
|
||||||
this.updatePageHeader();
|
|
||||||
this.updatePrimaryAction();
|
this.updatePrimaryAction();
|
||||||
if (!opts.fromPopstate) this.pushTabToUrl(tab);
|
if (!opts.fromPopstate) this.pushTabToUrl(tab);
|
||||||
}
|
}
|
||||||
@ -357,58 +355,6 @@ class BackupPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePageHeader() {
|
|
||||||
const titleEl = document.getElementById('backup-section-title');
|
|
||||||
const subEl = document.getElementById('backup-section-subtitle');
|
|
||||||
const iconEl = document.getElementById('backup-page-header-icon');
|
|
||||||
if (titleEl) titleEl.textContent = this.titleFor(this.currentTab);
|
|
||||||
if (subEl) subEl.textContent = this.subtitleFor(this.currentTab);
|
|
||||||
if (iconEl) iconEl.innerHTML = this.iconFor(this.currentTab);
|
|
||||||
}
|
|
||||||
|
|
||||||
titleFor(tab) {
|
|
||||||
return {
|
|
||||||
dashboard: 'Dashboard',
|
|
||||||
backups: 'Backups',
|
|
||||||
locations: 'Locations',
|
|
||||||
configuration: 'Configuration'
|
|
||||||
}[tab] || 'Backups';
|
|
||||||
}
|
|
||||||
|
|
||||||
subtitleFor(tab) {
|
|
||||||
return {
|
|
||||||
dashboard: "Check what's protected — and when it last ran.",
|
|
||||||
backups: 'Every backup across every enabled location.',
|
|
||||||
locations: 'Where backups are stored. Add, edit, or remove destinations.',
|
|
||||||
configuration: 'Schedule, retention, and engine settings.'
|
|
||||||
}[tab] || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
iconFor(tab) {
|
|
||||||
const icons = {
|
|
||||||
dashboard:
|
|
||||||
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
|
||||||
'<rect x="3" y="3" width="7" height="9"></rect>' +
|
|
||||||
'<rect x="14" y="3" width="7" height="5"></rect>' +
|
|
||||||
'<rect x="14" y="12" width="7" height="9"></rect>' +
|
|
||||||
'<rect x="3" y="16" width="7" height="5"></rect></svg>',
|
|
||||||
backups:
|
|
||||||
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
|
||||||
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>' +
|
|
||||||
'<polyline points="17 8 12 3 7 8"></polyline>' +
|
|
||||||
'<line x1="12" y1="3" x2="12" y2="15"></line></svg>',
|
|
||||||
locations:
|
|
||||||
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
|
||||||
'<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>' +
|
|
||||||
'<circle cx="12" cy="10" r="3"></circle></svg>',
|
|
||||||
configuration:
|
|
||||||
'<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
|
||||||
'<circle cx="12" cy="12" r="3"></circle>' +
|
|
||||||
'<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>'
|
|
||||||
};
|
|
||||||
return icons[tab] || icons.backups;
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePrimaryAction() {
|
updatePrimaryAction() {
|
||||||
const btn = document.getElementById('backup-primary-action');
|
const btn = document.getElementById('backup-primary-action');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
|
|||||||
@ -105,19 +105,15 @@
|
|||||||
/* App-page tab strips: anchor every tab bar to the same surface as
|
/* App-page tab strips: anchor every tab bar to the same surface as
|
||||||
the sidebar so the chrome reads as one coherent navy surface.
|
the sidebar so the chrome reads as one coherent navy surface.
|
||||||
Using var(--sidebar-bg) directly so any future sidebar tweak
|
Using var(--sidebar-bg) directly so any future sidebar tweak
|
||||||
carries through. Covers all three tab places on the apps page:
|
carries through. Covers both tab idioms on the apps page:
|
||||||
1. .tab-navigation — main app tabs (Config / Services / …) and
|
1. .tab-navigation — main app tabs (Config / Services / …) and
|
||||||
the Backup-location sub-tabs (Local / Remote 1 / Remote 2).
|
the Backup-location sub-tabs (Local / Remote 1 / Remote 2).
|
||||||
2. .tabs-list inside .tabs-wrapper — config sub-tabs that the
|
2. .tabs-list inside .tabs-wrapper — config sub-tabs from the
|
||||||
app-config form renders inside the Config tab.
|
app-config form, plus the fleet Overview's Migrate and Backups
|
||||||
3. The fleet Overview's Backups tab, where the embedded BackupPage
|
sub-tabs (all share the canonical .tabs-wrapper markup). */
|
||||||
sidebar is restyled into a nested horizontal strip + joined
|
|
||||||
content card (overview.css) — same surface, same anchor. */
|
|
||||||
[data-theme="nebula"] .tab-navigation,
|
[data-theme="nebula"] .tab-navigation,
|
||||||
[data-theme="nebula"] .tabs-wrapper .tabs-list,
|
[data-theme="nebula"] .tabs-wrapper .tabs-list,
|
||||||
[data-theme="nebula"] .tabs-content,
|
[data-theme="nebula"] .tabs-content {
|
||||||
[data-theme="nebula"] #overview-view #ov-pane-backups .backup-layout > .sidebar #backup-sidebar-list,
|
|
||||||
[data-theme="nebula"] #overview-view #ov-pane-backups .backup-layout > .main {
|
|
||||||
background: var(--sidebar-bg);
|
background: var(--sidebar-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user