Compare commits
2 Commits
86abfadb52
...
0e8f645334
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e8f645334 | ||
|
|
a06b6cd1d8 |
@ -42,6 +42,33 @@
|
||||
the area padding the old header used to provide at the top. */
|
||||
.overview-tabbed { margin-top: 0; padding: 22px 24px 24px; }
|
||||
|
||||
/* Tab header: title + blurb left, action buttons right, divider underneath —
|
||||
the same shape as the per-app .backup-title / .updater-title rows (the
|
||||
.config-title base supplies the 20px padding + bottom border). */
|
||||
#overview-view .ov-tab-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
#overview-view .ov-tab-header-main { flex: 1; min-width: 0; }
|
||||
#overview-view .ov-tab-header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Recessed dark panel holding each tab's body — same recipe as the per-app
|
||||
.tasks-container / .backup-snapshots-container / .updater-detail-container,
|
||||
so the fleet tabs read exactly like the app-detail tabs. */
|
||||
#overview-view .ov-tab-body {
|
||||
margin: 16px;
|
||||
padding: 16px;
|
||||
background: rgba(var(--bg-rgb), 0.2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* ---- Updates: filter chips + toolbar ------------------------------------ */
|
||||
.ov-toolbar {
|
||||
display: flex;
|
||||
@ -164,6 +191,11 @@
|
||||
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);
|
||||
|
||||
@ -140,34 +140,49 @@ class OverviewManager {
|
||||
}
|
||||
|
||||
// The shared in-content header. Sourced only from OV_TAB_META so every fleet
|
||||
// tab matches the per-app detail tabs' .config-title (emoji + title + blurb,
|
||||
// inside the pane, above the body). renderTab() prepends this for the string
|
||||
// tabs; mountMigrate() injects it once for the static Migrate pane.
|
||||
renderHeader(id) {
|
||||
// tab matches the per-app detail tabs' title block (emoji + title + blurb
|
||||
// left, action buttons right, divider underneath — the .backup-title shape).
|
||||
// renderTab() prepends this for the string tabs; mountMigrate() injects it
|
||||
// once for the static Migrate pane.
|
||||
renderHeader(id, actions = '') {
|
||||
const m = OV_TAB_META[id];
|
||||
if (!m) return '';
|
||||
return `<div class="config-title ov-tab-header"><h3>${m.icon} ${this.escape(m.title)}</h3><p>${this.escape(m.desc)}</p></div>`;
|
||||
return `<div class="config-title ov-tab-header">
|
||||
<div class="ov-tab-header-main"><h3>${m.icon} ${this.escape(m.title)}</h3><p>${this.escape(m.desc)}</p></div>
|
||||
${actions ? `<div class="ov-tab-header-actions">${actions}</div>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderTab(id) {
|
||||
const pane = document.querySelector(`#overview-view .tab-pane[data-tab="${id}"]`);
|
||||
if (!pane) return;
|
||||
// Every string-rendered tab leads with the shared in-content header, so the
|
||||
// body renderers below only ever produce the body (never a heading).
|
||||
// Every string-rendered tab follows the per-app detail tab idiom: the
|
||||
// shared header (title + actions + divider) on top, then the body inside
|
||||
// the recessed .ov-tab-body container — so the renderers below only ever
|
||||
// produce the body (never a heading or its action buttons).
|
||||
const checkBtn = (label) => `<button class="updater-btn" data-updater-action="check">↻ ${label}</button>`;
|
||||
const body = (html) => `<div class="ov-tab-body">${html}</div>`;
|
||||
switch (id) {
|
||||
case 'overview': pane.innerHTML = this.renderHeader(id) + this.renderOverview(); break;
|
||||
case 'overview':
|
||||
pane.innerHTML = this.renderHeader(id, checkBtn('Check now')) + body(this.renderOverview());
|
||||
break;
|
||||
case 'updates': {
|
||||
// Preserve which rows are expanded across a rebuild — a background
|
||||
// task-refresh repaints the whole table, so restore ALL open rows, not
|
||||
// just the single ?app= deep-link.
|
||||
const open = Array.from(document.querySelectorAll('#overview-view .ov-row-details:not([hidden])'))
|
||||
.map((d) => d.id.replace(/^ov-detail-/, ''));
|
||||
pane.innerHTML = this.renderHeader(id) + this.renderUpdates();
|
||||
const anyUpdate = !!(this.updater && this.updater.apps.some((a) => a.update_available));
|
||||
const actions = checkBtn('Check')
|
||||
+ (anyUpdate ? ` <button class="updater-btn updater-btn-primary" data-updater-action="update-all">Update all</button>` : '');
|
||||
pane.innerHTML = this.renderHeader(id, actions) + body(this.renderUpdates());
|
||||
open.forEach((app) => this._openDetail(app));
|
||||
this._honorAppDeepLink();
|
||||
break;
|
||||
}
|
||||
case 'improvements': pane.innerHTML = this.renderHeader(id) + this.renderImprovements(); break;
|
||||
case 'improvements':
|
||||
pane.innerHTML = this.renderHeader(id, checkBtn('Check')) + body(this.renderImprovements());
|
||||
break;
|
||||
case 'backups': this.mountBackupCenter(pane); break;
|
||||
case 'migrate': this.mountMigrate(); break;
|
||||
}
|
||||
@ -310,8 +325,7 @@ class OverviewManager {
|
||||
`<button class="updater-btn" data-overview-action="goto" data-tab="improvements">View</button>`)}
|
||||
${card('backups', b.protectedLabel, 'Backups protected', b.sub,
|
||||
`<button class="updater-btn" data-overview-action="goto" data-tab="backups">Backups</button>`)}
|
||||
${card('system', u.apps, 'Apps tracked', `last scan: ${checked}`,
|
||||
`<button class="updater-btn" data-updater-action="check">Check now</button>`)}
|
||||
${card('system', u.apps, 'Apps tracked', `last scan: ${checked}`)}
|
||||
</div>
|
||||
${(this.updater && this.updater.updates) ? '' : `<div class="updater-hint">No scan data yet — run <strong>Check now</strong> to fetch versions, CVEs & improvements.</div>`}`;
|
||||
}
|
||||
@ -341,19 +355,16 @@ class OverviewManager {
|
||||
const up = this.updater;
|
||||
if (!up || !up.apps.length) return `<div class="updater-empty">No installed apps to track.</div>`;
|
||||
const shown = this.filterApps(up.apps);
|
||||
const anyUpdate = up.apps.some((a) => a.update_available);
|
||||
const nUpd = up.apps.filter((a) => a.update_available).length;
|
||||
const nSec = up.apps.filter((a) => (a.cves || []).length).length;
|
||||
const chip = (id, label, n) =>
|
||||
`<button class="ov-chip${this.filter === id ? ' active' : ''}" data-overview-action="filter" data-filter="${id}">${label}${n != null ? ` <span class="ov-chip-n">${n}</span>` : ''}</button>`;
|
||||
const rows = shown.map((a) => this.updateRow(a)).join('') || `<div class="updater-empty">Nothing matches this filter.</div>`;
|
||||
// Check / Update all live in the tab header's action slot (renderTab), so
|
||||
// the toolbar is just the filter chips.
|
||||
return `
|
||||
<div class="updater-toolbar ov-toolbar">
|
||||
<div class="ov-chips">${chip('all', 'All', up.apps.length)}${chip('updates', 'Updates', nUpd)}${chip('security', 'Security', nSec)}</div>
|
||||
<div class="ov-toolbar-actions">
|
||||
<button class="updater-btn" data-updater-action="check">↻ Check</button>
|
||||
${anyUpdate ? `<button class="updater-btn updater-btn-primary" data-updater-action="update-all">Update all</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="updater-list ov-updates-list">${rows}</div>`;
|
||||
}
|
||||
@ -434,7 +445,9 @@ class OverviewManager {
|
||||
// ---- Improvements tab (reuse the updater's hotfix renderer) ---------------
|
||||
|
||||
renderImprovements() {
|
||||
return this.updater ? this.updater.renderImprovements() : `<div class="updater-empty">Improvements unavailable.</div>`;
|
||||
// withToolbar=false — the fleet tab's "Check" button lives in the shared
|
||||
// header action slot, so the embedded renderer skips its own toolbar.
|
||||
return this.updater ? this.updater.renderImprovements(false) : `<div class="updater-empty">Improvements unavailable.</div>`;
|
||||
}
|
||||
|
||||
// ---- Backups tab (fleet health glance; actions deep-link per app) ---------
|
||||
|
||||
@ -302,7 +302,9 @@ class UpdaterPage {
|
||||
<div class="updater-list">${rows}</div>`;
|
||||
}
|
||||
|
||||
renderImprovements() {
|
||||
// withToolbar=false lets an embedding surface (the fleet Overview tab) skip
|
||||
// the inline Check button because it provides one in its own header.
|
||||
renderImprovements(withToolbar = true) {
|
||||
if (!this.artifacts) return this.empty('No hotfix data yet. Run a check to fetch the signed improvements index.', true);
|
||||
const list = Array.isArray(this.artifacts.artifacts) ? this.artifacts.artifacts : [];
|
||||
const signed = !!this.artifacts.signed;
|
||||
@ -331,7 +333,7 @@ class UpdaterPage {
|
||||
? `<div class="updater-hint">Small, signed, individually-reversible improvements curated by the LibrePortal team. Security & breakage fixes apply automatically (a snapshot is taken first); the rest are one click. Every apply is logged in History and can be reverted.</div>`
|
||||
: `<div class="updater-hint">⚠ The improvements index is <strong>unsigned</strong> (signing not activated on this build) — applying is disabled for safety.</div>`;
|
||||
return `${banner}
|
||||
<div class="updater-toolbar"><button class="updater-btn" data-updater-action="check">↻ Check for improvements</button></div>
|
||||
${withToolbar ? `<div class="updater-toolbar"><button class="updater-btn" data-updater-action="check">↻ Check for improvements</button></div>` : ''}
|
||||
<div class="updater-list">${rows}</div>`;
|
||||
}
|
||||
|
||||
|
||||
@ -109,10 +109,15 @@
|
||||
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. */
|
||||
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. */
|
||||
[data-theme="nebula"] .tab-navigation,
|
||||
[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);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user