diff --git a/containers/libreportal/frontend/html/dashboard-content.html b/containers/libreportal/frontend/html/dashboard-content.html
index 5027fb3..fb1c568 100755
--- a/containers/libreportal/frontend/html/dashboard-content.html
+++ b/containers/libreportal/frontend/html/dashboard-content.html
@@ -46,7 +46,7 @@
-
+
Admin overview
diff --git a/containers/libreportal/frontend/html/topbar.html b/containers/libreportal/frontend/html/topbar.html
index e14d7a2..366abf0 100755
--- a/containers/libreportal/frontend/html/topbar.html
+++ b/containers/libreportal/frontend/html/topbar.html
@@ -31,7 +31,7 @@
App Center
-
+
diff --git a/containers/libreportal/frontend/js/components/admin/admin-overview.js b/containers/libreportal/frontend/js/components/admin/admin-overview.js
index edeb1fa..376f3c1 100644
--- a/containers/libreportal/frontend/js/components/admin/admin-overview.js
+++ b/containers/libreportal/frontend/js/components/admin/admin-overview.js
@@ -58,7 +58,7 @@ class AdminOverview {
window.librePortalSPA?.navigate('/backup', true);
} else if (where === 'ssh' || where === 'security') {
const target = where === 'ssh' ? 'ssh-access' : 'security';
- window.history.pushState({}, '', `/config?=${target}`);
+ window.history.pushState({}, '', window.adminPath(target));
window.configCategory = target;
window.configManager?.renderConfig?.(target);
}
diff --git a/containers/libreportal/frontend/js/components/config/config-sidebar.js b/containers/libreportal/frontend/js/components/config/config-sidebar.js
index d221766..c975f96 100755
--- a/containers/libreportal/frontend/js/components/config/config-sidebar.js
+++ b/containers/libreportal/frontend/js/components/config/config-sidebar.js
@@ -26,7 +26,7 @@ class ConfigSidebar {
overviewItem.setAttribute('data-category', 'overview');
overviewItem.innerHTML = ' Overview';
overviewItem.addEventListener('click', function () {
- window.history.pushState({}, '', '/config?=overview');
+ window.history.pushState({}, '', window.adminPath('overview'));
document.querySelectorAll('.category').forEach(function (item) { item.classList.remove('active'); });
this.classList.add('active');
window.configCategory = 'overview';
@@ -76,8 +76,7 @@ class ConfigSidebar {
categoryItem.addEventListener('click', function() {
// Update URL without full page reload
- const url = '/config?=' + category.id;
- window.history.pushState({}, '', url);
+ window.history.pushState({}, '', window.adminPath(category.id));
// Update active state
document.querySelectorAll('.category').forEach(function(item) {
@@ -109,7 +108,7 @@ class ConfigSidebar {
sshItem.setAttribute('data-category', 'ssh-access');
sshItem.innerHTML = '
SSH Access';
sshItem.addEventListener('click', function () {
- window.history.pushState({}, '', '/config?=ssh-access');
+ window.history.pushState({}, '', window.adminPath('ssh-access'));
document.querySelectorAll('.category').forEach(function (item) { item.classList.remove('active'); });
this.classList.add('active');
window.configCategory = 'ssh-access';
diff --git a/containers/libreportal/frontend/js/components/topbar.js b/containers/libreportal/frontend/js/components/topbar.js
index 082c843..329b9c8 100755
--- a/containers/libreportal/frontend/js/components/topbar.js
+++ b/containers/libreportal/frontend/js/components/topbar.js
@@ -17,7 +17,7 @@ class TopbarComponent {
if (path.startsWith('/apps') || path === '/apps') {
return 'apps';
}
- if (path.startsWith('/config') || path === '/config') {
+ if (path.startsWith('/admin') || path.startsWith('/config') || path.startsWith('/ssh')) {
return 'config';
}
if (path.startsWith('/tasks') || path === '/tasks') {
@@ -266,14 +266,12 @@ class TopbarComponent {
// PRIMARY: Use path-based detection only (most reliable)
if (path.startsWith('/app') || path.startsWith('/apps')) {
activeNavId = 'nav-app-center';
- } else if (path.startsWith('/config')) {
- activeNavId = 'nav-config';
+ } else if (path.startsWith('/admin') || path.startsWith('/config') || path.startsWith('/ssh')) {
+ activeNavId = 'nav-config'; // Admin area (config + SSH live here)
} else if (path.startsWith('/tasks')) {
activeNavId = 'nav-tasks';
} else if (path.startsWith('/backup')) {
activeNavId = 'nav-backup';
- } else if (path.startsWith('/ssh')) {
- activeNavId = 'nav-config'; // SSH Access lives under the Admin (config) area
} else if (path === '/' || path === '/dashboard') {
activeNavId = 'nav-dashboard';
} else {
@@ -349,7 +347,7 @@ class TopbarComponent {
if (path.startsWith('/app') || path.startsWith('/apps')) {
activeNavId = 'nav-app-center';
- } else if (path.startsWith('/config')) {
+ } else if (path.startsWith('/admin') || path.startsWith('/config') || path.startsWith('/ssh')) {
activeNavId = 'nav-config';
} else if (path.startsWith('/tasks')) {
activeNavId = 'nav-tasks';
diff --git a/containers/libreportal/frontend/js/spa.js b/containers/libreportal/frontend/js/spa.js
index 1ec72df..b9dc866 100755
--- a/containers/libreportal/frontend/js/spa.js
+++ b/containers/libreportal/frontend/js/spa.js
@@ -68,13 +68,15 @@ class LibrePortalSPAClean {
this.routes.set('/apps', () => this.handleApps());
this.routes.set('/app', () => this.handleAppDetail()); // Handle /app without query
this.routes.set('/app*', () => this.handleAppDetail()); // Handle /app with query
- this.routes.set('/config', () => this.handleConfig()); // Handle /config without query
- this.routes.set('/config*', () => this.handleConfig()); // Handle /config with query
+ this.routes.set('/admin', () => this.handleAdmin()); // Admin area (path-based: /admin/config/, /admin/tools/)
+ this.routes.set('/admin*', () => this.handleAdmin());
+ this.routes.set('/config', () => this.handleConfigRedirect()); // legacy → /admin
+ this.routes.set('/config*', () => this.handleConfigRedirect());
this.routes.set('/tasks', () => this.handleTasks()); // Handle /tasks without query
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('/ssh', () => this.handleSsh());
+ this.routes.set('/ssh', () => this.handleSsh()); // legacy → /admin/tools/ssh-access
this.routes.set('/ssh*', () => this.handleSsh());
//console.log('📍 Routes registered:', Array.from(this.routes.keys()));
@@ -270,9 +272,8 @@ class LibrePortalSPAClean {
}
async handleSsh() {
- // SSH Access now lives inside the Admin (config) area as a sidebar item.
- // Redirect old /ssh links to it.
- this.navigate('/config?=ssh-access', true);
+ // Legacy /ssh → SSH Access under the Admin area.
+ this.navigate('/admin/tools/ssh-access', true);
}
async handleApps() {
@@ -363,43 +364,41 @@ class LibrePortalSPAClean {
}
}
- async handleConfig() {
- //console.log('⚙️ Loading config...');
-
- // Handle query parameters for config
- const path = window.location.pathname + window.location.search;
- if (path.includes('?=')) {
- const [basePath, query] = path.split('?=');
- window.configCategory = query || 'overview';
- } else if (path.includes('?')) {
- const url = new URL(path, window.location.origin);
- const searchParams = url.searchParams;
- window.configCategory = searchParams.get('config') || 'overview';
- } else {
- window.configCategory = 'overview';
- }
-
+ // Admin area. Path-based: /admin (overview), /admin/config/,
+ // /admin/tools/ssh-access. Reuses the config page shell + ConfigManager.
+ async handleAdmin() {
+ window.configCategory = window.adminCategoryFromPath(window.location.pathname);
+
try {
const html = await this.fetchContent('/html/config-content.html');
- this.loadContent(html, 'Configuration');
-
- // Config manager should already be initialized by SystemLoader
+ this.loadContent(html, 'Admin');
+
if (window.configManager) {
- // Render the actual configuration
if (typeof window.configManager.renderConfig === 'function') {
await window.configManager.renderConfig(window.configCategory || 'overview');
}
- //console.log('✅ Config loaded');
} else {
console.error('ConfigManager not available - SystemLoader should have initialized it');
throw new Error('ConfigManager not initialized by SystemLoader');
}
} catch (error) {
- console.error('❌ Config load error:', error);
- this.showError('Failed to load configuration');
+ console.error('❌ Admin load error:', error);
+ this.showError('Failed to load the Admin area');
}
}
+ // Legacy /config and /config?= → the path-based /admin equivalent.
+ async handleConfigRedirect() {
+ const search = window.location.search || '';
+ let cat = 'overview';
+ if (search.includes('?=')) {
+ cat = (window.location.pathname + search).split('?=')[1] || 'overview';
+ } else {
+ cat = new URLSearchParams(search).get('config') || 'overview';
+ }
+ this.navigate(window.adminPath(cat), true);
+ }
+
async handleTasks() {
//console.log('📋 Loading tasks...');
@@ -476,7 +475,7 @@ class LibrePortalSPAClean {
if (path.startsWith('/app') || path.startsWith('/apps')) {
activeId = 'nav-app-center';
- } else if (path.startsWith('/config')) {
+ } else if (path.startsWith('/admin') || path.startsWith('/config') || path.startsWith('/ssh')) {
activeId = 'nav-config';
} else if (path.startsWith('/tasks')) {
activeId = 'nav-tasks';
@@ -507,6 +506,21 @@ class LibrePortalSPAClean {
}
}
+// Admin area path helpers (shared by the SPA, sidebar, overview, ssh page).
+// 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 === 'ssh-access') return '/admin/tools/ssh-access';
+ return '/admin/config/' + category;
+};
+window.adminCategoryFromPath = function (pathname) {
+ const segs = String(pathname || '').replace(/^\/admin\/?/, '').split('/').filter(Boolean);
+ if (!segs.length || segs[0] === 'overview') return 'overview';
+ if (segs[0] === 'config') return segs[1] || 'general';
+ if (segs[0] === 'tools') return segs[1] || 'overview';
+ return segs[0];
+};
+
// Global navigation function for click handlers
window.navigateToRoute = function(href) {
if (window.spaClean) {
@@ -522,8 +536,8 @@ window.navigateToRoute = function(href) {
route = '/dashboard';
} else if (route === 'apps') {
route = '/apps';
- } else if (route === 'config') {
- route = '/config?=general';
+ } else if (route === 'config' || route === 'admin') {
+ route = '/admin';
} else if (route === 'tasks') {
route = '/tasks';
} else if (!route.startsWith('/')) {
diff --git a/containers/libreportal/frontend/js/utils/data-loader.js b/containers/libreportal/frontend/js/utils/data-loader.js
index b517708..ffe1d28 100755
--- a/containers/libreportal/frontend/js/utils/data-loader.js
+++ b/containers/libreportal/frontend/js/utils/data-loader.js
@@ -150,8 +150,8 @@ async function initializeData() {
} else if (currentPath === '/apps' || currentPath === '/app' || searchParams.has('apps') || searchParams.has('app')) {
// Apps page: All apps + categories (no system configs needed)
await loadAppsPageData();
- } else if (currentPath.startsWith('/config') || searchParams.has('config')) {
- // Config pages: System configs + apps + categories
+ } else if (currentPath.startsWith('/admin') || currentPath.startsWith('/config') || currentPath.startsWith('/ssh') || searchParams.has('config')) {
+ // Admin area (config + SSH): system configs + apps + categories
await loadConfigDetailData();
} else {
// Default: Load all data for SPA
diff --git a/containers/libreportal/frontend/js/utils/router.js b/containers/libreportal/frontend/js/utils/router.js
index 3901d7c..64f5e86 100755
--- a/containers/libreportal/frontend/js/utils/router.js
+++ b/containers/libreportal/frontend/js/utils/router.js
@@ -264,7 +264,7 @@ class Router {
// PRIMARY: Use path-based detection only (most reliable)
if (pathname.startsWith('/app') || pathname.startsWith('/apps')) {
activeNavId = 'nav-app-center';
- } else if (pathname.startsWith('/config')) {
+ } else if (pathname.startsWith('/admin') || pathname.startsWith('/config') || pathname.startsWith('/ssh')) {
activeNavId = 'nav-config';
} else if (pathname.startsWith('/tasks')) {
activeNavId = 'nav-tasks';