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 <librelad@digitalangels.vip>
174 lines
8.0 KiB
JavaScript
Executable File
174 lines
8.0 KiB
JavaScript
Executable File
// Config Sidebar - Handles sidebar population and navigation
|
|
class ConfigSidebar {
|
|
constructor() {
|
|
this.categoriesList = null;
|
|
}
|
|
|
|
populateSidebar() {
|
|
//console.log('ConfigSidebar: Populating sidebar with categories...');
|
|
|
|
this.categoriesList = document.getElementById('config-categories-list');
|
|
if (!this.categoriesList) {
|
|
console.error('ConfigSidebar: config-categories-list element not found');
|
|
return;
|
|
}
|
|
|
|
if (!window.configData || !window.configData.categories) {
|
|
console.error('ConfigSidebar: No config data available for sidebar');
|
|
return;
|
|
}
|
|
|
|
this.categoriesList.innerHTML = '';
|
|
|
|
// Overview — the Admin landing (an ops/health board, not a config form).
|
|
const overviewItem = document.createElement('div');
|
|
overviewItem.className = 'category';
|
|
overviewItem.setAttribute('data-category', 'overview');
|
|
overviewItem.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:8px;vertical-align:middle"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg> Overview';
|
|
overviewItem.addEventListener('click', function () {
|
|
window.history.pushState({}, '', window.adminPath('overview'));
|
|
document.querySelectorAll('.category').forEach(function (item) { item.classList.remove('active'); });
|
|
this.classList.add('active');
|
|
window.configCategory = 'overview';
|
|
if (window.configManager && typeof window.configManager.renderConfig === 'function') {
|
|
window.configManager.renderConfig('overview');
|
|
}
|
|
});
|
|
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 = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:8px;vertical-align:middle"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg> 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');
|
|
configLabel.className = 'sidebar-group-label';
|
|
configLabel.textContent = 'Config';
|
|
this.categoriesList.appendChild(configLabel);
|
|
|
|
// Convert categories object to array and sort by ORDER
|
|
const categoriesArray = Object.entries(window.configData.categories).map(([key, value]) => ({
|
|
id: key,
|
|
...value
|
|
}));
|
|
|
|
// Sort by ORDER if available, otherwise by title
|
|
categoriesArray.sort(function(a, b) {
|
|
const orderA = parseInt(a.order) || 999;
|
|
const orderB = parseInt(b.order) || 999;
|
|
return orderA - orderB;
|
|
});
|
|
|
|
var self = this; // Preserve 'this' context
|
|
|
|
categoriesArray.forEach(function(category) {
|
|
// Backup category has its own top-level page (/backup) which renders
|
|
// these same fields dynamically — hide it from the /config sidebar to
|
|
// avoid two surfaces for the same data.
|
|
if (category.id === 'backup') return;
|
|
|
|
const categoryItem = document.createElement('div');
|
|
categoryItem.className = 'category';
|
|
categoryItem.setAttribute('data-category', category.id);
|
|
|
|
// Use correct icon from our new structure
|
|
const iconName = category.icon || category.id;
|
|
const iconPath = '/icons/config/' + iconName + '.svg';
|
|
|
|
categoryItem.innerHTML = '<img src="' + iconPath + '" alt="' + category.title + '" style="width: 20px; height: 20px; margin-right: 8px;"/> ' + category.title;
|
|
|
|
categoryItem.addEventListener('click', function() {
|
|
// Update URL without full page reload
|
|
window.history.pushState({}, '', window.adminPath(category.id));
|
|
|
|
// Update active state
|
|
document.querySelectorAll('.category').forEach(function(item) {
|
|
item.classList.remove('active');
|
|
});
|
|
this.classList.add('active');
|
|
|
|
// Update global category and load dynamically
|
|
window.configCategory = category.id;
|
|
|
|
// Load config dynamically without page refresh
|
|
if (window.configManager && typeof window.configManager.renderConfig === 'function') {
|
|
window.configManager.renderConfig(category.id);
|
|
}
|
|
});
|
|
|
|
self.categoriesList.appendChild(categoryItem);
|
|
});
|
|
|
|
// Tools group — admin pages that live in this area but aren't config
|
|
// categories (rendered by their own controller, not the config form).
|
|
const toolsLabel = document.createElement('div');
|
|
toolsLabel.className = 'sidebar-group-label';
|
|
toolsLabel.textContent = 'Tools';
|
|
self.categoriesList.appendChild(toolsLabel);
|
|
|
|
const sshItem = document.createElement('div');
|
|
sshItem.className = 'category';
|
|
sshItem.setAttribute('data-category', 'ssh-access');
|
|
sshItem.innerHTML = '<img src="/icons/config/security.svg" alt="SSH Access" style="width: 20px; height: 20px; margin-right: 8px;"/> SSH Access';
|
|
sshItem.addEventListener('click', function () {
|
|
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';
|
|
if (window.configManager && typeof window.configManager.renderConfig === 'function') {
|
|
window.configManager.renderConfig('ssh-access');
|
|
}
|
|
});
|
|
self.categoriesList.appendChild(sshItem);
|
|
|
|
const peersItem = document.createElement('div');
|
|
peersItem.className = 'category';
|
|
peersItem.setAttribute('data-category', 'peers');
|
|
peersItem.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:8px;vertical-align:middle"><circle cx="6" cy="7" r="3"></circle><circle cx="18" cy="7" r="3"></circle><path d="M9 17l3-3 3 3"></path><path d="M12 14v7"></path><path d="M6 10v3a3 3 0 003 3"></path><path d="M18 10v3a3 3 0 01-3 3"></path></svg> 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 = 'peers';
|
|
if (window.configManager && typeof window.configManager.renderConfig === 'function') {
|
|
window.configManager.renderConfig('peers');
|
|
}
|
|
});
|
|
self.categoriesList.appendChild(peersItem);
|
|
|
|
// Set initial active category
|
|
this.setActiveCategory(window.configCategory || 'overview');
|
|
|
|
//console.log('ConfigSidebar: Sidebar populated with ' + categoriesArray.length + ' categories');
|
|
}
|
|
|
|
setActiveCategory(categoryId) {
|
|
// Update active state
|
|
document.querySelectorAll('.category').forEach(function(item) {
|
|
item.classList.remove('active');
|
|
});
|
|
var activeItem = document.querySelector('[data-category="' + categoryId + '"]');
|
|
if (activeItem) {
|
|
activeItem.classList.add('active');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export to global scope
|
|
window.ConfigSidebar = ConfigSidebar;
|