fix(webui/backups): sync fleet Backups sub-tab URLs (/apps/overview/backups/<sub>)
The embedded backup center deep-links *in* fine (mountBackupCenter parses the sub-segment), but clicking a sub-tab never wrote the URL: BackupPage disables pushTabToUrl() when embedded and nothing on the fleet side stepped in, so the address bar stayed stale and the sub-tabs weren't shareable/deep-linkable — unlike the sibling Migrate sub-tabs. Give BackupPage an onTabChange hook fired on every switch; the OverviewManager supplies it to replaceState /apps/overview/backups/<sub> (replaceState, matching Migrate, so the on-entry deep-link re-sync can't stack a duplicate history entry). Standalone /backup keeps using pushTabToUrl() unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
ff711d3c2b
commit
73a93cf145
@ -643,7 +643,16 @@ class OverviewManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try { if (window.overviewBackupPage) window.overviewBackupPage.dispose(); } catch (_) {}
|
try { if (window.overviewBackupPage) window.overviewBackupPage.dispose(); } catch (_) {}
|
||||||
window.overviewBackupPage = new BackupPage({ embedded: true });
|
// Own the URL for the embedded center's sub-tabs: reflect each switch as
|
||||||
|
// /apps/overview/backups/<sub> so they're shareable + deep-linkable (the
|
||||||
|
// read side is already handled by mountBackupCenter's segment parse). Use
|
||||||
|
// replaceState, mirroring the Migrate sub-tabs — the switchTab() we fire
|
||||||
|
// on entry to honor a deep link would otherwise stack a duplicate history
|
||||||
|
// entry on top of the one we navigated in on.
|
||||||
|
window.overviewBackupPage = new BackupPage({
|
||||||
|
embedded: true,
|
||||||
|
onTabChange: (sub) => this._pushUrl(`/apps/overview/backups/${sub}`, true),
|
||||||
|
});
|
||||||
await window.overviewBackupPage.init();
|
await window.overviewBackupPage.init();
|
||||||
if (sub) { try { window.overviewBackupPage.switchTab(sub); } catch (_) {} }
|
if (sub) { try { window.overviewBackupPage.switchTab(sub); } catch (_) {} }
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|||||||
@ -8,8 +8,14 @@
|
|||||||
class BackupPage {
|
class BackupPage {
|
||||||
constructor(opts = {}) {
|
constructor(opts = {}) {
|
||||||
// Embedded in the fleet Overview's Backups tab: the same controller, but
|
// Embedded in the fleet Overview's Backups tab: the same controller, but
|
||||||
// it must not couple to the /backup URL (the fleet owns /overview/backups).
|
// it must not write the standalone /backup URL — the fleet owns the
|
||||||
|
// address bar. It hands us onTabChange so a sub-tab switch still syncs the
|
||||||
|
// canonical /apps/overview/backups/<sub> URL via the OverviewManager.
|
||||||
this.embedded = !!opts.embedded;
|
this.embedded = !!opts.embedded;
|
||||||
|
// Host-supplied when embedded: called with the new sub-tab slug after
|
||||||
|
// every switch so the host can reflect it in the URL. Null for the
|
||||||
|
// standalone page, which uses pushTabToUrl() instead.
|
||||||
|
this.onTabChange = typeof opts.onTabChange === 'function' ? opts.onTabChange : null;
|
||||||
this.currentTab = 'dashboard';
|
this.currentTab = 'dashboard';
|
||||||
this.dashboard = null;
|
this.dashboard = null;
|
||||||
this.locations = null;
|
this.locations = null;
|
||||||
@ -37,7 +43,10 @@ class BackupPage {
|
|||||||
and /backup?backup=dashboard (standard query string) so links from
|
and /backup?backup=dashboard (standard query string) so links from
|
||||||
either source resolve correctly. */
|
either source resolve correctly. */
|
||||||
parseTabFromUrl() {
|
parseTabFromUrl() {
|
||||||
if (this.embedded) return null; // embedded: always open on Dashboard; sub-tabs are in-page only
|
// Embedded: the fleet host (OverviewManager.mountBackupCenter) parses the
|
||||||
|
// /apps/overview/backups/<sub> segment and drives switchTab, so the
|
||||||
|
// embedded controller never reads the URL itself.
|
||||||
|
if (this.embedded) return null;
|
||||||
const allowed = new Set(['dashboard', 'backups', 'locations', 'configuration']);
|
const allowed = new Set(['dashboard', 'backups', 'locations', 'configuration']);
|
||||||
// Path-based: /backup/<tab> (bare /backup → default tab).
|
// Path-based: /backup/<tab> (bare /backup → default tab).
|
||||||
const seg = window.location.pathname.replace(/^\/backup\/?/, '').split('/')[0];
|
const seg = window.location.pathname.replace(/^\/backup\/?/, '').split('/')[0];
|
||||||
@ -339,11 +348,18 @@ class BackupPage {
|
|||||||
this.currentTab = tab;
|
this.currentTab = tab;
|
||||||
this.applyActiveTabUi(tab);
|
this.applyActiveTabUi(tab);
|
||||||
this.updatePrimaryAction();
|
this.updatePrimaryAction();
|
||||||
if (!opts.fromPopstate) this.pushTabToUrl(tab);
|
if (!opts.fromPopstate) {
|
||||||
|
// Standalone writes /backup/<tab> itself; embedded lets the fleet host
|
||||||
|
// map the switch onto /apps/overview/backups/<tab> via onTabChange (its
|
||||||
|
// own pushTabToUrl is a no-op). Both are skipped when the switch was
|
||||||
|
// triggered by a popstate — the URL has already moved.
|
||||||
|
this.pushTabToUrl(tab);
|
||||||
|
if (this.onTabChange) { try { this.onTabChange(tab); } catch (_) {} }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pushTabToUrl(tab) {
|
pushTabToUrl(tab) {
|
||||||
if (this.embedded) return; // embedded: keep the URL at /overview/backups (no sub-tab coupling)
|
if (this.embedded) return; // embedded: the fleet host owns the URL (see onTabChange)
|
||||||
const url = `/backup/${tab}`;
|
const url = `/backup/${tab}`;
|
||||||
// Use replaceState for the *first* push (initial tab inferred from
|
// Use replaceState for the *first* push (initial tab inferred from
|
||||||
// URL); otherwise pushState so back/forward navigates between tabs.
|
// URL); otherwise pushState so back/forward navigates between tabs.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user