refactor(routing): move System out of /admin/config to /admin/system
System is live stats, not configuration, so it shouldn't live under
/admin/config. adminPath('system') now emits /admin/system; the path
parser locates 'system' positionally; all nav targets, breadcrumbs and the
dashboard disk-card link point at /admin/system{,/storage,/metric/<k>}.
adminCategoryFromPath already resolves /admin/<x> to that category, so
ConfigManager still mounts AdminSystem unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
18134a3ee1
commit
bbbd035ab2
@ -3,10 +3,10 @@
|
||||
// One AdminSystem instance per page mount. Reads the URL path on init and
|
||||
// dispatches to one of four sub-views:
|
||||
//
|
||||
// /admin/config/system → index (gauges + trends + per-app table)
|
||||
// /admin/config/system/metric/<key> → single-metric deep-dive page
|
||||
// /admin/config/system/app/<name> → per-container app deep-dive
|
||||
// /admin/config/system/storage → Docker disk breakdown
|
||||
// /admin/system → index (gauges + trends + per-app table)
|
||||
// /admin/system/metric/<key> → single-metric deep-dive page
|
||||
// /admin/system/app/<name> → per-container app deep-dive
|
||||
// /admin/system/storage → Docker disk breakdown
|
||||
//
|
||||
// Sub-views are separate page renderers (system-metric-page.js etc.) that
|
||||
// each own their own DOM + lifecycle inside #config-section. We mount one
|
||||
@ -32,16 +32,19 @@ class AdminSystem {
|
||||
|
||||
root() { return document.getElementById(this.rootId); }
|
||||
|
||||
// Path → view dispatch. AdminPath base is /admin/config/system; sub-paths
|
||||
// Path → view dispatch. AdminPath base is /admin/system; sub-paths
|
||||
// add segments after that. Falls through to 'index' for an unknown shape
|
||||
// so a typo'd URL doesn't blank the page.
|
||||
_parsePath() {
|
||||
const segs = String(window.location.pathname || '').split('/').filter(Boolean);
|
||||
// segs = ['admin','config','system', ...]
|
||||
const sub = segs[3];
|
||||
if (sub === 'metric' && segs[4]) return { view: 'metric', key: decodeURIComponent(segs[4]) };
|
||||
if (sub === 'app' && segs[4]) return { view: 'app', name: decodeURIComponent(segs[4]) };
|
||||
if (sub === 'storage') return { view: 'storage' };
|
||||
// Locate 'system' and read the sub-view after it, so dispatch works for
|
||||
// /admin/system/<sub>/<arg> regardless of leading segments.
|
||||
const i = segs.indexOf('system');
|
||||
const sub = i >= 0 ? segs[i + 1] : undefined;
|
||||
const arg = i >= 0 ? segs[i + 2] : undefined;
|
||||
if (sub === 'metric' && arg) return { view: 'metric', key: decodeURIComponent(arg) };
|
||||
if (sub === 'app' && arg) return { view: 'app', name: decodeURIComponent(arg) };
|
||||
if (sub === 'storage') return { view: 'storage' };
|
||||
return { view: 'index' };
|
||||
}
|
||||
|
||||
@ -136,7 +139,7 @@ class AdminSystem {
|
||||
const ex = e.target.closest('[data-sys-expand]');
|
||||
if (ex) {
|
||||
const k = ex.dataset.sysExpand;
|
||||
if (window.navigateToRoute) window.navigateToRoute(`/admin/config/system/metric/${encodeURIComponent(k)}`);
|
||||
if (window.navigateToRoute) window.navigateToRoute(`/admin/system/metric/${encodeURIComponent(k)}`);
|
||||
return;
|
||||
}
|
||||
const ap = e.target.closest('[data-sys-app]');
|
||||
@ -151,7 +154,7 @@ class AdminSystem {
|
||||
}
|
||||
const st = e.target.closest('[data-sys-storage]');
|
||||
if (st) {
|
||||
if (window.navigateToRoute) window.navigateToRoute('/admin/config/system/storage');
|
||||
if (window.navigateToRoute) window.navigateToRoute('/admin/system/storage');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Admin → System → Metric — single-metric deep-dive PAGE.
|
||||
//
|
||||
// Replaces the previous fullscreen overlay (system-detail.js) with a real
|
||||
// in-flow page mounted at /admin/config/system/metric/<key>. Bookmarkable,
|
||||
// in-flow page mounted at /admin/system/metric/<key>. Bookmarkable,
|
||||
// browser-back navigates, refresh keeps you here.
|
||||
//
|
||||
// Renders into #config-section as a full-width admin page. Owns its own
|
||||
@ -85,7 +85,7 @@ class SystemMetricPage {
|
||||
|
||||
_onKey(e) {
|
||||
if (e.key === 'Escape' && window.navigateToRoute) {
|
||||
window.navigateToRoute('/admin/config/system');
|
||||
window.navigateToRoute('/admin/system');
|
||||
}
|
||||
}
|
||||
_onResize() { this._renderChart(); }
|
||||
@ -111,12 +111,12 @@ class SystemMetricPage {
|
||||
}
|
||||
const back = e.target.closest('[data-back]');
|
||||
if (back && window.navigateToRoute) {
|
||||
window.navigateToRoute('/admin/config/system');
|
||||
window.navigateToRoute('/admin/system');
|
||||
return;
|
||||
}
|
||||
const go = e.target.closest('[data-go-storage]');
|
||||
if (go && window.navigateToRoute) {
|
||||
window.navigateToRoute('/admin/config/system/storage');
|
||||
window.navigateToRoute('/admin/system/storage');
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +151,7 @@ class SystemMetricPage {
|
||||
<div class="page-header config-page-header">
|
||||
<div class="page-header-title">
|
||||
<div class="admin-breadcrumb">
|
||||
<a href="/admin/config/system" data-back>Admin · System</a>
|
||||
<a href="/admin/system" data-back>Admin · System</a>
|
||||
</div>
|
||||
<h1>Unknown metric</h1>
|
||||
<p>No metric registered under "${(window.SystemFmt?.escape || String)(this.metricKey)}".</p>
|
||||
@ -169,7 +169,7 @@ class SystemMetricPage {
|
||||
<div class="page-header config-page-header">
|
||||
<div class="page-header-title">
|
||||
<div class="admin-breadcrumb">
|
||||
<a href="/admin/config/system" data-back>Admin · System</a>
|
||||
<a href="/admin/system" data-back>Admin · System</a>
|
||||
</div>
|
||||
<h1 class="sys-metric-name">${fmt.escape(this.metric.label)}</h1>
|
||||
<p class="sys-metric-sub">${fmt.escape(this._subline())}</p>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Admin → System → Storage — where the disk is going.
|
||||
//
|
||||
// Mounted at /admin/config/system/storage. Two data sources:
|
||||
// Mounted at /admin/system/storage. Two data sources:
|
||||
//
|
||||
// - Storage by app — on-disk size of each app's bind-mounted data, from the
|
||||
// generated /data/system/app_storage.json (du, not docker — see the
|
||||
@ -59,7 +59,7 @@ class SystemStoragePage {
|
||||
_onClick(e) {
|
||||
const back = e.target.closest('[data-back]');
|
||||
if (back && window.navigateToRoute) {
|
||||
window.navigateToRoute('/admin/config/system');
|
||||
window.navigateToRoute('/admin/system');
|
||||
return;
|
||||
}
|
||||
const recl = e.target.closest('[data-storage-reclaim]');
|
||||
@ -155,7 +155,7 @@ class SystemStoragePage {
|
||||
<div class="page-header config-page-header">
|
||||
<div class="page-header-title">
|
||||
<div class="admin-breadcrumb">
|
||||
<a href="/admin/config/system" data-back>Admin · System</a>
|
||||
<a href="/admin/system" data-back>Admin · System</a>
|
||||
</div>
|
||||
<h1>Storage</h1>
|
||||
<p class="sys-storage-sub">On-disk space by app, plus Docker's own engine usage.</p>
|
||||
|
||||
@ -551,6 +551,7 @@ class LibrePortalSPAClean {
|
||||
// Map a category to its path-based URL, and parse a path back to a category.
|
||||
window.adminPath = function (category) {
|
||||
if (!category || category === 'overview') return '/admin';
|
||||
if (category === 'system') return '/admin/system'; // stats, not config
|
||||
if (category === 'ssh-access') return '/admin/tools/ssh-access';
|
||||
if (category === 'peers') return '/admin/tools/peers';
|
||||
return '/admin/config/' + category;
|
||||
|
||||
@ -453,7 +453,7 @@ async function loadSystemInfo() {
|
||||
// addEventListener) so a dashboard re-mount can't stack duplicate handlers.
|
||||
const diskCardEl = document.getElementById('disk-stat-card');
|
||||
if (diskCardEl) {
|
||||
const goStorage = () => window.navigateToRoute && window.navigateToRoute('/admin/config/system/storage');
|
||||
const goStorage = () => window.navigateToRoute && window.navigateToRoute('/admin/system/storage');
|
||||
diskCardEl.onclick = goStorage;
|
||||
diskCardEl.onkeydown = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); goStorage(); } };
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user