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) {