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; }
|
||||
|
||||
/* ---- 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
|
||||
|
||||
@ -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('<div class="mobile-overlay" id="mobile-overlay"></div>', '');
|
||||
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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,74 +1,26 @@
|
||||
<div class="container backup-layout">
|
||||
<div class="mobile-overlay" id="mobile-overlay"></div>
|
||||
|
||||
<div class="sidebar" id="sidebar">
|
||||
<div class="sidebar-section" id="backup-sidebar-list">
|
||||
<div class="category active" data-backup-tab="dashboard">
|
||||
<svg class="category-icon" 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>
|
||||
Dashboard
|
||||
</div>
|
||||
<div class="category" data-backup-tab="backups">
|
||||
<svg class="category-icon" 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>
|
||||
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 class="backup-layout">
|
||||
<!-- Canonical sub-tab idiom (.tabs-wrapper > .tabs-list + .tabs-content),
|
||||
identical to the per-app Config tab and the Migrate tab, so the backup
|
||||
center reads as tabs-within-tabs with no bespoke chrome. -->
|
||||
<div class="tabs-wrapper">
|
||||
<div class="tabs-list">
|
||||
<button type="button" class="tab-button active" data-backup-tab="dashboard">
|
||||
<span class="tab-emoji">📊</span><span class="tab-name">Dashboard</span>
|
||||
</button>
|
||||
<button type="button" class="tab-button" data-backup-tab="backups">
|
||||
<span class="tab-emoji">💾</span><span class="tab-name">Backups</span>
|
||||
</button>
|
||||
<button type="button" class="tab-button" data-backup-tab="locations">
|
||||
<span class="tab-emoji">📍</span><span class="tab-name">Locations</span>
|
||||
</button>
|
||||
<button type="button" class="tab-button" data-backup-tab="configuration">
|
||||
<span class="tab-emoji">⚙️</span><span class="tab-name">Configuration</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="backup-page" id="backup-page">
|
||||
<div class="config-section backup-page-section">
|
||||
<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>
|
||||
<div class="tabs-content">
|
||||
<div class="backup-page" id="backup-page">
|
||||
<span class="backup-engine-badge" id="backup-engine-badge" hidden>restic</span>
|
||||
|
||||
<section class="backup-tabpanel active" id="backup-panel-dashboard">
|
||||
<div class="backup-summary-row" id="backup-summary-row"></div>
|
||||
@ -116,7 +68,32 @@
|
||||
<section class="backup-tabpanel" id="backup-panel-configuration">
|
||||
<div id="backup-configuration-body"></div>
|
||||
</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>
|
||||
|
||||
@ -29,7 +29,6 @@ class BackupPage {
|
||||
await this.refreshAll();
|
||||
await window.Dismissible?.load();
|
||||
this.render();
|
||||
this.updatePageHeader();
|
||||
this.updatePrimaryAction();
|
||||
}
|
||||
|
||||
@ -53,11 +52,11 @@ class BackupPage {
|
||||
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
|
||||
browser back/forward). */
|
||||
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);
|
||||
});
|
||||
document.querySelectorAll('.backup-tabpanel').forEach(p => {
|
||||
@ -104,7 +103,7 @@ class BackupPage {
|
||||
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) {
|
||||
this.switchTab(tabBtn.dataset.backupTab);
|
||||
return;
|
||||
@ -339,7 +338,6 @@ class BackupPage {
|
||||
if (!tab || tab === this.currentTab) return;
|
||||
this.currentTab = tab;
|
||||
this.applyActiveTabUi(tab);
|
||||
this.updatePageHeader();
|
||||
this.updatePrimaryAction();
|
||||
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() {
|
||||
const btn = document.getElementById('backup-primary-action');
|
||||
if (!btn) return;
|
||||
|
||||
@ -105,19 +105,15 @@
|
||||
/* App-page tab strips: anchor every tab bar to the same surface as
|
||||
the sidebar so the chrome reads as one coherent navy surface.
|
||||
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
|
||||
the Backup-location sub-tabs (Local / Remote 1 / Remote 2).
|
||||
2. .tabs-list inside .tabs-wrapper — config sub-tabs that the
|
||||
app-config form renders inside the Config tab.
|
||||
3. The fleet Overview's Backups tab, where the embedded BackupPage
|
||||
sidebar is restyled into a nested horizontal strip + joined
|
||||
content card (overview.css) — same surface, same anchor. */
|
||||
2. .tabs-list inside .tabs-wrapper — config sub-tabs from the
|
||||
app-config form, plus the fleet Overview's Migrate and Backups
|
||||
sub-tabs (all share the canonical .tabs-wrapper markup). */
|
||||
[data-theme="nebula"] .tab-navigation,
|
||||
[data-theme="nebula"] .tabs-wrapper .tabs-list,
|
||||
[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 {
|
||||
[data-theme="nebula"] .tabs-content {
|
||||
background: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user