Compare commits
2 Commits
de5621746d
...
9a58869899
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a58869899 | ||
|
|
0bcde854e6 |
@ -70,7 +70,7 @@ class OverviewPage {
|
|||||||
|
|
||||||
go(where) {
|
go(where) {
|
||||||
if (where === 'backup') {
|
if (where === 'backup') {
|
||||||
window.spaClean?.navigate('/backup', true);
|
window.spaClean?.navigate('/apps/overview/backups', true);
|
||||||
} else if (where === 'ssh' || where === 'security' || where === 'system') {
|
} else if (where === 'ssh' || where === 'security' || where === 'system') {
|
||||||
const target = where === 'ssh' ? 'ssh-access' : where;
|
const target = where === 'ssh' ? 'ssh-access' : where;
|
||||||
window.history.pushState({}, '', window.adminPath(target));
|
window.history.pushState({}, '', window.adminPath(target));
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<!-- Fleet Overview entry — pinned above the search box, opens the
|
<!-- Fleet Overview entry — pinned above the search box, opens the
|
||||||
Overview · Updates · Improvements · Backups tabs in the main pane. -->
|
Overview · Updates · Improvements · Backups tabs in the main pane. -->
|
||||||
<div class="sidebar-overview-entry" id="sidebar-overview-entry" role="button" tabindex="0"
|
<div class="sidebar-overview-entry" id="sidebar-overview-entry" role="button" tabindex="0"
|
||||||
onclick="if(window.navigateToRoute){window.navigateToRoute('/overview');}else{window.location.href='/overview';}"
|
onclick="if(window.navigateToRoute){window.navigateToRoute('/apps/overview');}else{window.location.href='/apps/overview';}"
|
||||||
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();this.click();}">
|
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();this.click();}">
|
||||||
<svg class="ov-entry-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
<svg class="ov-entry-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
<rect x="3" y="3" width="7" height="9"></rect>
|
<rect x="3" y="3" width="7" height="9"></rect>
|
||||||
@ -210,7 +210,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Backup now
|
Backup now
|
||||||
</button>
|
</button>
|
||||||
<a class="btn btn-secondary" href="/backup" onclick="event.preventDefault(); if(window.navigateToRoute){window.navigateToRoute('/backup');}else{window.location.href='/backup';}">
|
<a class="btn btn-secondary" href="/apps/overview/backups" onclick="event.preventDefault(); if(window.navigateToRoute){window.navigateToRoute('/apps/overview/backups');}else{window.location.href='/apps/overview/backups';}">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||||
<polyline points="15 3 21 3 21 9"></polyline>
|
<polyline points="15 3 21 3 21 9"></polyline>
|
||||||
|
|||||||
@ -497,9 +497,9 @@ class AppsManager {
|
|||||||
chip.style.cursor = 'pointer';
|
chip.style.cursor = 'pointer';
|
||||||
chip.style.display = '';
|
chip.style.display = '';
|
||||||
chip.onclick = () => {
|
chip.onclick = () => {
|
||||||
if (typeof window.navigateToRoute === 'function') window.navigateToRoute('/overview/improvements');
|
if (typeof window.navigateToRoute === 'function') window.navigateToRoute('/apps/overview/improvements');
|
||||||
else if (typeof window.spaClean === 'function') window.spaClean('/overview/improvements');
|
else if (typeof window.spaClean === 'function') window.spaClean('/apps/overview/improvements');
|
||||||
else window.location.href = '/overview/improvements';
|
else window.location.href = '/apps/overview/improvements';
|
||||||
};
|
};
|
||||||
} catch (_) { /* best-effort */ }
|
} catch (_) { /* best-effort */ }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,9 +4,7 @@
|
|||||||
"/apps",
|
"/apps",
|
||||||
"/apps*",
|
"/apps*",
|
||||||
"/app",
|
"/app",
|
||||||
"/app*",
|
"/app*"
|
||||||
"/overview",
|
|
||||||
"/overview*"
|
|
||||||
],
|
],
|
||||||
"module": "/components/apps/index.js",
|
"module": "/components/apps/index.js",
|
||||||
"handler": "handleApps",
|
"handler": "handleApps",
|
||||||
|
|||||||
@ -6,12 +6,15 @@
|
|||||||
// sibling component; it's the same feature, so it lives here.)
|
// sibling component; it's the same feature, so it lives here.)
|
||||||
LP.features.register({
|
LP.features.register({
|
||||||
id: 'apps',
|
id: 'apps',
|
||||||
routes: ['/apps', '/apps*', '/app', '/app*', '/overview', '/overview*'],
|
routes: ['/apps', '/apps*', '/app', '/app*'],
|
||||||
|
|
||||||
async mount(ctx) {
|
async mount(ctx) {
|
||||||
// /overview* -> fleet Overview; /apps* -> grid; everything else (/app*) ->
|
// /apps/overview* -> fleet Overview; /apps* -> grid; everything else
|
||||||
// detail. Check '/apps' before '/app' (since '/apps'.startsWith('/app')).
|
// (/app*) -> detail. The Overview check must precede the bare '/apps' one
|
||||||
if (window.location.pathname.startsWith('/overview')) {
|
// (since '/apps/overview'.startsWith('/apps')), and '/apps' before '/app'
|
||||||
|
// (since '/apps'.startsWith('/app')). Legacy /overview* is rewritten to
|
||||||
|
// /apps/overview* by _legacyRedirect() before this runs.
|
||||||
|
if (window.location.pathname.startsWith('/apps/overview')) {
|
||||||
return this._mountOverview(ctx);
|
return this._mountOverview(ctx);
|
||||||
}
|
}
|
||||||
if (window.location.pathname.startsWith('/apps')) {
|
if (window.location.pathname.startsWith('/apps')) {
|
||||||
@ -78,7 +81,7 @@ LP.features.register({
|
|||||||
await window.appTabbedManager.initialize();
|
await window.appTabbedManager.initialize();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ---- fleet Overview (/overview[/<tab>]) ----
|
// ---- fleet Overview (/apps/overview[/<tab>]) ----
|
||||||
async _mountOverview(ctx) {
|
async _mountOverview(ctx) {
|
||||||
// Lazy-load the fleet controller + its deps. Guard by typeof so re-entry
|
// Lazy-load the fleet controller + its deps. Guard by typeof so re-entry
|
||||||
// never re-declares the classes (loadScripts dedupes by URL too). The
|
// never re-declares the classes (loadScripts dedupes by URL too). The
|
||||||
|
|||||||
@ -124,7 +124,7 @@ class OverviewManager {
|
|||||||
|
|
||||||
parseTabFromUrl() {
|
parseTabFromUrl() {
|
||||||
const allowed = new Set(['overview', 'updates', 'improvements', 'backups', 'migrate']);
|
const allowed = new Set(['overview', 'updates', 'improvements', 'backups', 'migrate']);
|
||||||
const seg = window.location.pathname.replace(/^\/overview\/?/, '').split('/')[0];
|
const seg = window.location.pathname.replace(/^\/apps\/overview\/?/, '').split('/')[0];
|
||||||
return (seg && allowed.has(seg)) ? seg : null;
|
return (seg && allowed.has(seg)) ? seg : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ class OverviewManager {
|
|||||||
switchTab(id) {
|
switchTab(id) {
|
||||||
if (!id || id === this.current) return;
|
if (!id || id === this.current) return;
|
||||||
this._applyTab(id);
|
this._applyTab(id);
|
||||||
const url = id === 'overview' ? '/overview' : `/overview/${id}`;
|
const url = id === 'overview' ? '/apps/overview' : `/apps/overview/${id}`;
|
||||||
this._pushUrl(url, false);
|
this._pushUrl(url, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +212,7 @@ class OverviewManager {
|
|||||||
if (pane && !pane.querySelector(':scope > .ov-tab-header')) {
|
if (pane && !pane.querySelector(':scope > .ov-tab-header')) {
|
||||||
pane.insertAdjacentHTML('afterbegin', this.renderHeader('migrate'));
|
pane.insertAdjacentHTML('afterbegin', this.renderHeader('migrate'));
|
||||||
}
|
}
|
||||||
const seg = window.location.pathname.replace(/^\/overview\/migrate\/?/, '').split('/')[0];
|
const seg = window.location.pathname.replace(/^\/apps\/overview\/migrate\/?/, '').split('/')[0];
|
||||||
const sub = (seg === 'peers' || seg === 'restore') ? seg : (this._migrateSub || 'restore');
|
const sub = (seg === 'peers' || seg === 'restore') ? seg : (this._migrateSub || 'restore');
|
||||||
this.switchMigrateSub(sub, { fromUrl: true });
|
this.switchMigrateSub(sub, { fromUrl: true });
|
||||||
}
|
}
|
||||||
@ -225,7 +225,7 @@ class OverviewManager {
|
|||||||
pane.querySelectorAll('.ov-subtabs-content .tab-panel[data-ov-subtab]').forEach((p) => p.classList.toggle('active', p.dataset.ovSubtab === sub));
|
pane.querySelectorAll('.ov-subtabs-content .tab-panel[data-ov-subtab]').forEach((p) => p.classList.toggle('active', p.dataset.ovSubtab === sub));
|
||||||
if (sub === 'restore') this._mountRestore();
|
if (sub === 'restore') this._mountRestore();
|
||||||
else if (sub === 'peers') this._mountPeers();
|
else if (sub === 'peers') this._mountPeers();
|
||||||
if (!opts.fromUrl) this._pushUrl(`/overview/migrate/${sub}`, true);
|
if (!opts.fromUrl) this._pushUrl(`/apps/overview/migrate/${sub}`, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _mountRestore() {
|
async _mountRestore() {
|
||||||
@ -601,9 +601,9 @@ class OverviewManager {
|
|||||||
// configuration sections (its sidebar restyled as a nested tab strip). On a
|
// configuration sections (its sidebar restyled as a nested tab strip). On a
|
||||||
// revisit we refresh rather than re-mount, to keep its sub-tab + expand state.
|
// revisit we refresh rather than re-mount, to keep its sub-tab + expand state.
|
||||||
mountBackupCenter(pane) {
|
mountBackupCenter(pane) {
|
||||||
// Honor an optional sub-tab deep-link (/overview/backups/<sub>), e.g. the
|
// Honor an optional sub-tab deep-link (/apps/overview/backups/<sub>), e.g.
|
||||||
// Restore empty-state's "Open Locations" button.
|
// the Restore empty-state's "Open Locations" button.
|
||||||
const seg = window.location.pathname.replace(/^\/overview\/backups\/?/, '').split('/')[0];
|
const seg = window.location.pathname.replace(/^\/apps\/overview\/backups\/?/, '').split('/')[0];
|
||||||
const sub = ['dashboard', 'backups', 'locations', 'configuration'].includes(seg) ? seg : null;
|
const sub = ['dashboard', 'backups', 'locations', 'configuration'].includes(seg) ? seg : null;
|
||||||
// Detect a prior mount by the embedded fragment in THIS pane — not by
|
// Detect a prior mount by the embedded fragment in THIS pane — not by
|
||||||
// #backup-section, which the per-app Backups tab also defines.
|
// #backup-section, which the per-app Backups tab also defines.
|
||||||
|
|||||||
@ -68,7 +68,7 @@ class MigratePage {
|
|||||||
}
|
}
|
||||||
const locBtn = e.target.closest('[data-action="go-to-locations"]');
|
const locBtn = e.target.closest('[data-action="go-to-locations"]');
|
||||||
if (locBtn && this.root()?.contains(locBtn)) {
|
if (locBtn && this.root()?.contains(locBtn)) {
|
||||||
if (window.navigateToRoute) window.navigateToRoute('/overview/backups/locations');
|
if (window.navigateToRoute) window.navigateToRoute('/apps/overview/backups/locations');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.target.closest('#ov-migrate-confirm')) { this.confirmMigrate(); return; }
|
if (e.target.closest('#ov-migrate-confirm')) { this.confirmMigrate(); return; }
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "backup",
|
|
||||||
"routes": ["/backup", "/backup*"],
|
|
||||||
"module": "/components/backup/index.js",
|
|
||||||
"handler": "handleBackup",
|
|
||||||
"navId": "nav-backup",
|
|
||||||
"order": 50
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
// components/backup/index.js — Backup Center as a self-contained feature module.
|
|
||||||
//
|
|
||||||
// FIRST page migrated to the feature-module contract (docs/architecture/webui-architecture.md).
|
|
||||||
// The kernel drives mount()/unmount() for the /backup route instead of
|
|
||||||
// spa.js's handleBackup(). The heavy controller (backup-page.js, ~129KB) is
|
|
||||||
// still lazy-loaded on first mount, so cold-load cost is unchanged.
|
|
||||||
//
|
|
||||||
// Snap-out demo: deleting this folder removes the backup route's module
|
|
||||||
// registration; the manifest entry then falls back to the legacy handler
|
|
||||||
// (and, once handleBackup is retired, to the kernel's not-found route). The
|
|
||||||
// full decomposition of backup-page.js into per-tab modules is Phase 5.
|
|
||||||
LP.features.register({
|
|
||||||
id: 'backup',
|
|
||||||
routes: ['/backup', '/backup*'],
|
|
||||||
// Controllers the feature needs; lazy-loaded on first mount (idempotent).
|
|
||||||
// Controllers, organised by sub-system (tabs). core/ first: schema + base
|
|
||||||
// class + the shared data/cron, then each tab's prototype-augment clusters.
|
|
||||||
scripts: [
|
|
||||||
'/components/backup/core/js/backup-schema.js',
|
|
||||||
'/components/backup/core/js/backup-page.js', // base: class + constructor + init/switchTab/render
|
|
||||||
'/components/backup/core/js/backup-fetch-client.js',
|
|
||||||
'/components/backup/core/js/backup-cron-schedule.js',
|
|
||||||
'/components/backup/dashboard/js/backup-dashboard.js',
|
|
||||||
'/components/backup/snapshots/js/backup-snapshots.js',
|
|
||||||
'/components/backup/snapshots/js/backup-snapshot-actions.js',
|
|
||||||
'/components/backup/locations/js/backup-locations.js',
|
|
||||||
'/components/backup/locations/js/backup-location-fields.js',
|
|
||||||
'/components/backup/locations/js/backup-location-modal.js',
|
|
||||||
'/components/backup/locations/js/backup-ssh-key.js',
|
|
||||||
'/components/backup/configuration/js/backup-configuration.js',
|
|
||||||
'/components/backup/configuration/js/backup-retention-presets.js',
|
|
||||||
'/components/backup/configuration/js/backup-engine-details.js',
|
|
||||||
'/core/backup-card/js/backup-app-card.js',
|
|
||||||
],
|
|
||||||
|
|
||||||
async mount(ctx) {
|
|
||||||
await ctx.loadScripts(this.scripts);
|
|
||||||
const html = await ctx.loadFragment('/components/backup/core/html/backup-content.html');
|
|
||||||
ctx.setContent(html, 'Backups');
|
|
||||||
if (typeof BackupPage === 'undefined') {
|
|
||||||
throw new Error('BackupPage controller failed to load');
|
|
||||||
}
|
|
||||||
window.backupPage = new BackupPage();
|
|
||||||
await window.backupPage.init();
|
|
||||||
},
|
|
||||||
|
|
||||||
async unmount(ctx) {
|
|
||||||
// Release the page's document listeners + task-refresh registration so a
|
|
||||||
// navigation away doesn't leave stale BackupPage listeners firing on the
|
|
||||||
// live DOM — the backup sidebar "content stacks on revisit" bug. dispose()
|
|
||||||
// aborts the click/input/change listeners and drops the coordinator reg.
|
|
||||||
try { window.backupPage && window.backupPage.dispose(); } catch (_) {}
|
|
||||||
try { ctx.services.tasks.refresh && ctx.services.tasks.refresh.unregister('backups'); } catch (_) {}
|
|
||||||
window.backupPage = null;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -23,9 +23,7 @@
|
|||||||
"/apps",
|
"/apps",
|
||||||
"/apps*",
|
"/apps*",
|
||||||
"/app",
|
"/app",
|
||||||
"/app*",
|
"/app*"
|
||||||
"/overview",
|
|
||||||
"/overview*"
|
|
||||||
],
|
],
|
||||||
"module": "/components/apps/index.js",
|
"module": "/components/apps/index.js",
|
||||||
"handler": "handleApps",
|
"handler": "handleApps",
|
||||||
@ -75,17 +73,6 @@
|
|||||||
"module": "/components/updater/index.js",
|
"module": "/components/updater/index.js",
|
||||||
"navId": "nav-updater",
|
"navId": "nav-updater",
|
||||||
"order": 30
|
"order": 30
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "backup",
|
|
||||||
"routes": [
|
|
||||||
"/backup",
|
|
||||||
"/backup*"
|
|
||||||
],
|
|
||||||
"module": "/components/backup/index.js",
|
|
||||||
"handler": "handleBackup",
|
|
||||||
"navId": "nav-backup",
|
|
||||||
"order": 50
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -379,7 +379,10 @@ class UpdaterPage {
|
|||||||
|
|
||||||
renderSecurity() {
|
renderSecurity() {
|
||||||
const withCves = this.apps.filter(a => (a.cves || []).length);
|
const withCves = this.apps.filter(a => (a.cves || []).length);
|
||||||
if (!this.cves) return this.empty('No vulnerability scan yet — one runs automatically within a couple of minutes.', true);
|
// No inline Check button: the host auto-scan runs the vulnerability scan on
|
||||||
|
// its own within a couple of minutes (and the embedding header carries a
|
||||||
|
// manual Check), so the message alone is the right button-free empty UI.
|
||||||
|
if (!this.cves) return this.empty('No vulnerability scan yet — one runs automatically within a couple of minutes.');
|
||||||
if (!withCves.length) return this.empty('No known vulnerabilities in your installed apps. 🎉');
|
if (!withCves.length) return this.empty('No known vulnerabilities in your installed apps. 🎉');
|
||||||
const blocks = withCves.map(a => {
|
const blocks = withCves.map(a => {
|
||||||
const items = (a.cves || []).map(c => `
|
const items = (a.cves || []).map(c => `
|
||||||
|
|||||||
@ -80,7 +80,7 @@ class BackupAppCard {
|
|||||||
<div class="backup-snapshot-rows">
|
<div class="backup-snapshot-rows">
|
||||||
${allSnaps.slice(0, 50).map(s => this._renderRow(s, iconUrl)).join('')}
|
${allSnaps.slice(0, 50).map(s => this._renderRow(s, iconUrl)).join('')}
|
||||||
</div>
|
</div>
|
||||||
${allSnaps.length > 50 ? `<div class="backup-snapshot-overflow">Showing the most recent 50 of ${allSnaps.length} backups. Use the <a href="/backup">backup center</a> for the full list.</div>` : ''}
|
${allSnaps.length > 50 ? `<div class="backup-snapshot-overflow">Showing the most recent 50 of ${allSnaps.length} backups. Use the <a href="/apps/overview/backups">backup center</a> for the full list.</div>` : ''}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Deep-link: /app/<name>/backups?snapshot=<id> auto-expands that row
|
// Deep-link: /app/<name>/backups?snapshot=<id> auto-expands that row
|
||||||
|
|||||||
@ -69,10 +69,9 @@ class LibrePortalSPAClean {
|
|||||||
this.routes.set('/admin*', () => this.handleAdmin());
|
this.routes.set('/admin*', () => this.handleAdmin());
|
||||||
this.routes.set('/tasks', () => this.handleTasks()); // Handle /tasks without query
|
this.routes.set('/tasks', () => this.handleTasks()); // Handle /tasks without query
|
||||||
this.routes.set('/tasks*', () => this.handleTasks()); // Handle /tasks with query
|
this.routes.set('/tasks*', () => this.handleTasks()); // Handle /tasks with query
|
||||||
this.routes.set('/backup', () => this.handleBackup());
|
// Legacy /config, /peers, /ssh, /backup, /overview are handled by
|
||||||
this.routes.set('/backup*', () => this.handleBackup());
|
// _legacyRedirect() at the top of navigate() (rewritten to their canonical
|
||||||
// Legacy /config, /peers, /ssh are handled by _legacyRedirect() at the top
|
// /admin/* or /apps/overview* paths before routing).
|
||||||
// of navigate() (rewrites to the canonical /admin/* path).
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,28 +332,6 @@ class LibrePortalSPAClean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleBackup() {
|
|
||||||
try {
|
|
||||||
// backup-page.js + backup-app-card.js are loaded on first navigation.
|
|
||||||
// loadScript is idempotent — subsequent /backup navigations are no-ops.
|
|
||||||
await Promise.all([
|
|
||||||
this.loadScript('/components/backup/core/js/backup-page.js'),
|
|
||||||
this.loadScript('/core/backup-card/js/backup-app-card.js')
|
|
||||||
]);
|
|
||||||
const html = await this.fetchContent('/components/backup/core/html/backup-content.html');
|
|
||||||
this.loadContent(html, 'Backups');
|
|
||||||
if (typeof BackupPage !== 'undefined') {
|
|
||||||
window.backupPage = new BackupPage();
|
|
||||||
await window.backupPage.init();
|
|
||||||
} else {
|
|
||||||
console.error('BackupPage class not loaded');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Backup page load error:', error);
|
|
||||||
this.showError('Failed to load backup page');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map a legacy short URL (/ssh, /peers, /config[?=cat]) to its canonical
|
// Map a legacy short URL (/ssh, /peers, /config[?=cat]) to its canonical
|
||||||
// /admin/* path, or return null if it isn't a legacy redirect. Used at the top
|
// /admin/* path, or return null if it isn't a legacy redirect. Used at the top
|
||||||
// of navigate(). Replaces the old config-redirect/peers/ssh handlers.
|
// of navigate(). Replaces the old config-redirect/peers/ssh handlers.
|
||||||
@ -362,8 +339,8 @@ class LibrePortalSPAClean {
|
|||||||
const full = path || '';
|
const full = path || '';
|
||||||
const p = full.split('?')[0];
|
const p = full.split('?')[0];
|
||||||
if (p === '/ssh' || p.startsWith('/ssh/')) return '/admin/tools/ssh-access';
|
if (p === '/ssh' || p.startsWith('/ssh/')) return '/admin/tools/ssh-access';
|
||||||
// Peers now lives under Overview › Migrate › Peers (moved out of Admin).
|
// Peers now lives under App Center › Overview › Migrate › Peers (moved out of Admin).
|
||||||
if (p === '/peers' || p.startsWith('/peers/') || p === '/admin/tools/peers' || p.startsWith('/admin/tools/peers/')) return '/overview/migrate/peers';
|
if (p === '/peers' || p.startsWith('/peers/') || p === '/admin/tools/peers' || p.startsWith('/admin/tools/peers/')) return '/apps/overview/migrate/peers';
|
||||||
if (p === '/config' || p.startsWith('/config/') || full.startsWith('/config?')) {
|
if (p === '/config' || p.startsWith('/config/') || full.startsWith('/config?')) {
|
||||||
let cat = 'overview';
|
let cat = 'overview';
|
||||||
if (full.includes('?=')) cat = full.split('?=')[1] || 'overview';
|
if (full.includes('?=')) cat = full.split('?=')[1] || 'overview';
|
||||||
@ -371,15 +348,27 @@ class LibrePortalSPAClean {
|
|||||||
else { const seg = p.replace(/^\/config\/?/, ''); if (seg) cat = seg; }
|
else { const seg = p.replace(/^\/config\/?/, ''); if (seg) cat = seg; }
|
||||||
return (typeof window.adminPath === 'function') ? window.adminPath(cat) : '/admin';
|
return (typeof window.adminPath === 'function') ? window.adminPath(cat) : '/admin';
|
||||||
}
|
}
|
||||||
// The standalone Updater page is now the fleet Overview area. /updater[/tab]
|
// The fleet Overview area lives under App Center: /apps/overview[/tab].
|
||||||
// -> /overview[/tab]; security/recovery/history folded into the Updates
|
// Rewrite the legacy short forms so old bookmarks/links keep working and the
|
||||||
// expander, so they land on /overview/updates. (/backup is NOT redirected —
|
// address bar always shows the canonical path:
|
||||||
// it remains the operational backup center, reached from Overview › Backups.)
|
// /overview[/tab] -> /apps/overview[/tab]
|
||||||
|
// /backup[/sub] -> /apps/overview/backups[/sub] (the backup center is
|
||||||
|
// now the Overview › Backups tab, not a standalone page)
|
||||||
|
// /updater[/tab] -> /apps/overview[/tab]; security/recovery/history were
|
||||||
|
// folded into the Updates expander, so they land on
|
||||||
|
// /apps/overview/updates.
|
||||||
|
if (p === '/overview' || p.startsWith('/overview/')) {
|
||||||
|
return '/apps' + p;
|
||||||
|
}
|
||||||
|
if (p === '/backup' || p.startsWith('/backup/')) {
|
||||||
|
const sub = p.replace(/^\/backup\/?/, '');
|
||||||
|
return sub ? '/apps/overview/backups/' + sub : '/apps/overview/backups';
|
||||||
|
}
|
||||||
if (p === '/updater' || p.startsWith('/updater/')) {
|
if (p === '/updater' || p.startsWith('/updater/')) {
|
||||||
const seg = p.replace(/^\/updater\/?/, '');
|
const seg = p.replace(/^\/updater\/?/, '');
|
||||||
if (seg === 'updates' || seg === 'improvements') return '/overview/' + seg;
|
if (seg === 'updates' || seg === 'improvements') return '/apps/overview/' + seg;
|
||||||
if (!seg || seg === 'overview') return '/overview';
|
if (!seg || seg === 'overview') return '/apps/overview';
|
||||||
return '/overview/updates';
|
return '/apps/overview/updates';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user