From cfdd39386cc13f5eac0f463c84b4cc2d47f26f7e Mon Sep 17 00:00:00 2001 From: librelad Date: Tue, 26 May 2026 20:16:45 +0100 Subject: [PATCH] feat(admin): move Peers into Admin/Tools; lift System next to Overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related UI tidies — both removing surface area from the topbar / Tools group rather than adding new pages. Peers → /admin/tools/peers Was a top-level /peers route with its own topbar nav item, which doubled the navigation surface for what's really an admin tool (same shape as SSH Access). Now lives under the Admin sidebar's Tools group alongside SSH Access. /peers is kept as a legacy redirect → /admin/tools/peers. Plumbing: - config-sidebar.js gains a Peers entry under the Tools label. - config-manager.js gains a 'peers' branch that fetches peers-content.html into config-section, then inits PeersPage. - window.adminPath() learns 'peers' → /admin/tools/peers. - spa.js handlePeers() is now a redirect (mirrors handleSsh). - topbar.html drops the Peers nav item. - peers-content.html slimmed to a config-section template (no standalone page wrapper) so it embeds cleanly under the admin shell. - PeersPage gains a rootId constructor arg for symmetry with SshPage (queries still work globally — IDs are unique). System lifted out of the Tools group User feedback: 'overview/system are kinda like, the same thing'. Moved System to sit right under Overview at the top of the sidebar, before the 'Config' label. Both surfaces are admin-landing pages (Overview = ops/health summary, System = live host + per-app stats) — distinct from config form pages or the Tools utilities. config-sidebar.js: System block moved to the top section (right after Overview's click handler). Original Tools-group instance removed. Signed-off-by: librelad --- .../frontend/html/peers-content.html | 107 ++++++++---------- .../libreportal/frontend/html/topbar.html | 11 -- .../js/components/config/config-manager.js | 22 ++++ .../js/components/config/config-sidebar.js | 36 ++++-- .../js/components/peers/peers-page.js | 7 +- containers/libreportal/frontend/js/spa.js | 18 +-- 6 files changed, 105 insertions(+), 96 deletions(-) diff --git a/containers/libreportal/frontend/html/peers-content.html b/containers/libreportal/frontend/html/peers-content.html index c8a6d83..cee326c 100644 --- a/containers/libreportal/frontend/html/peers-content.html +++ b/containers/libreportal/frontend/html/peers-content.html @@ -1,66 +1,51 @@ -
-
-
-
- - - - -
-
+
+ + + + +
diff --git a/containers/libreportal/frontend/html/topbar.html b/containers/libreportal/frontend/html/topbar.html index b5b733a..a5ef5ae 100755 --- a/containers/libreportal/frontend/html/topbar.html +++ b/containers/libreportal/frontend/html/topbar.html @@ -51,17 +51,6 @@ Backups - - - - - - - - - - Peers -
diff --git a/containers/libreportal/frontend/js/components/config/config-manager.js b/containers/libreportal/frontend/js/components/config/config-manager.js index 689ebc5..e1f2ca5 100755 --- a/containers/libreportal/frontend/js/components/config/config-manager.js +++ b/containers/libreportal/frontend/js/components/config/config-manager.js @@ -57,6 +57,28 @@ if (typeof window.ConfigManager === 'undefined') { return; } + // Peers is an admin tool page — named references to other LibrePortal + // instances (kind=backup-channel for shared-backup migrate, or + // kind=direct-ssh-direct for live SSH pulls). Same shape as SSH Access: + // we inject its content template, then init PeersPage. + if (category === 'peers') { + try { this.sidebar.populateSidebar(); } catch (e) {} + try { + const html = await fetch('/html/peers-content.html').then(r => r.text()); + configSection.innerHTML = html; + } catch (e) { + configSection.innerHTML = '
Peers page template failed to load.
'; + return; + } + if (typeof PeersPage !== 'undefined') { + window.peersPage = new PeersPage('config-section'); + await window.peersPage.init(); + } else { + configSection.innerHTML = '
PeersPage controller not loaded.
'; + } + return; + } + // System is an admin tool page (live host + per-app statistics) with its // own controller, like SSH Access above. if (category === 'system') { diff --git a/containers/libreportal/frontend/js/components/config/config-sidebar.js b/containers/libreportal/frontend/js/components/config/config-sidebar.js index 7f2e5ae..8024a1c 100755 --- a/containers/libreportal/frontend/js/components/config/config-sidebar.js +++ b/containers/libreportal/frontend/js/components/config/config-sidebar.js @@ -36,6 +36,24 @@ class ConfigSidebar { }); this.categoriesList.appendChild(overviewItem); + // System sits right under Overview — both are admin-landing surfaces + // (Overview = ops/health summary, System = live host + per-app stats), + // distinct from the config form pages or the Tools utilities below. + const systemItem = document.createElement('div'); + systemItem.className = 'category'; + systemItem.setAttribute('data-category', 'system'); + systemItem.innerHTML = ' System'; + systemItem.addEventListener('click', function () { + window.history.pushState({}, '', window.adminPath('system')); + document.querySelectorAll('.category').forEach(function (item) { item.classList.remove('active'); }); + this.classList.add('active'); + window.configCategory = 'system'; + if (window.configManager && typeof window.configManager.renderConfig === 'function') { + window.configManager.renderConfig('system'); + } + }); + this.categoriesList.appendChild(systemItem); + // "Config" group heading above the configuration categories (mirrors the // "Tools" heading below). const configLabel = document.createElement('div'); @@ -118,20 +136,20 @@ class ConfigSidebar { }); self.categoriesList.appendChild(sshItem); - const systemItem = document.createElement('div'); - systemItem.className = 'category'; - systemItem.setAttribute('data-category', 'system'); - systemItem.innerHTML = ' System'; - systemItem.addEventListener('click', function () { - window.history.pushState({}, '', window.adminPath('system')); + const peersItem = document.createElement('div'); + peersItem.className = 'category'; + peersItem.setAttribute('data-category', 'peers'); + peersItem.innerHTML = ' Peers'; + peersItem.addEventListener('click', function () { + window.history.pushState({}, '', window.adminPath('peers')); document.querySelectorAll('.category').forEach(function (item) { item.classList.remove('active'); }); this.classList.add('active'); - window.configCategory = 'system'; + window.configCategory = 'peers'; if (window.configManager && typeof window.configManager.renderConfig === 'function') { - window.configManager.renderConfig('system'); + window.configManager.renderConfig('peers'); } }); - self.categoriesList.appendChild(systemItem); + self.categoriesList.appendChild(peersItem); // Set initial active category this.setActiveCategory(window.configCategory || 'overview'); diff --git a/containers/libreportal/frontend/js/components/peers/peers-page.js b/containers/libreportal/frontend/js/components/peers/peers-page.js index f1d4cde..a2f5351 100644 --- a/containers/libreportal/frontend/js/components/peers/peers-page.js +++ b/containers/libreportal/frontend/js/components/peers/peers-page.js @@ -4,7 +4,12 @@ // kind=backup-channel; the other kinds light up in Phase 3. class PeersPage { - constructor() { + constructor(rootId = 'config-section') { + // rootId is the container the page renders into when embedded under + // /admin/tools/peers. The peers-* element IDs are unique enough that + // global document.getElementById queries still find them inside the + // injected template — but keeping the ref for future scoped queries. + this.rootId = rootId; this.peers = []; this.backupLocations = []; // populated for the loc_idx dropdown this.taskManager = (typeof TaskManager !== 'undefined') ? new TaskManager() : null; diff --git a/containers/libreportal/frontend/js/spa.js b/containers/libreportal/frontend/js/spa.js index 7e00169..b31935c 100755 --- a/containers/libreportal/frontend/js/spa.js +++ b/containers/libreportal/frontend/js/spa.js @@ -77,7 +77,7 @@ class LibrePortalSPAClean { this.routes.set('/tasks*', () => this.handleTasks()); // Handle /tasks with query this.routes.set('/backup', () => this.handleBackup()); this.routes.set('/backup*', () => this.handleBackup()); - this.routes.set('/peers', () => this.handlePeers()); + this.routes.set('/peers', () => this.handlePeers()); // legacy → /admin/tools/peers this.routes.set('/peers*', () => this.handlePeers()); this.routes.set('/ssh', () => this.handleSsh()); // legacy → /admin/tools/ssh-access this.routes.set('/ssh*', () => this.handleSsh()); @@ -275,19 +275,8 @@ class LibrePortalSPAClean { } async handlePeers() { - try { - const html = await this.fetchContent('/html/peers-content.html'); - this.loadContent(html, 'Peers'); - if (typeof PeersPage !== 'undefined') { - window.peersPage = new PeersPage(); - await window.peersPage.init(); - } else { - console.error('PeersPage class not loaded'); - } - } catch (error) { - console.error('❌ Peers page load error:', error); - this.showError('Failed to load peers page'); - } + // Legacy /peers → Peers under the Admin area. + this.navigate('/admin/tools/peers', true); } async handleSsh() { @@ -533,6 +522,7 @@ class LibrePortalSPAClean { window.adminPath = function (category) { if (!category || category === 'overview') return '/admin'; if (category === 'ssh-access') return '/admin/tools/ssh-access'; + if (category === 'peers') return '/admin/tools/peers'; return '/admin/config/' + category; }; window.adminCategoryFromPath = function (pathname) {