secubox-openwrt/package/secubox/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js
CyberMind-FR c5e22fd08d refactor(nav): Unify navigation component with auto-theme initialization
SecuNav.renderTabs() now automatically initializes theme and loads CSS,
eliminating boilerplate from views. Added renderCompactTabs() for nested
modules and renderBreadcrumb() for back-navigation.

Updated module navs: cdn-cache, client-guardian, crowdsec-dashboard,
media-flow, mqtt-bridge, system-hub. Removed ~1000 lines of duplicate CSS.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 03:42:32 +01:00

407 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require baseclass';
'require secubox-theme/theme as Theme';
/**
* SecuBox Main Navigation Widget
*
* Unified navigation component that handles:
* - Theme initialization (auto-calls Theme.init())
* - CSS loading (idempotent)
* - Main SecuBox tabs (dashboard, modules, settings, etc.)
* - Compact variant for nested modules (CDN Cache, Network Modes, etc.)
*
* Usage:
* // Main SecuBox views - just call renderTabs(), no need to require Theme separately
* SecuNav.renderTabs('dashboard')
*
* // Nested module views - use renderCompactTabs() with custom tab definitions
* SecuNav.renderCompactTabs('overview', [
* { id: 'overview', icon: '📦', label: 'Overview', path: ['admin', 'services', 'cdn-cache', 'overview'] },
* { id: 'cache', icon: '💾', label: 'Cache', path: ['admin', 'services', 'cdn-cache', 'cache'] }
* ])
*/
// Immediately inject CSS to hide LuCI tabs before page renders
(function() {
if (typeof document === 'undefined') return;
if (document.getElementById('secubox-early-hide')) return;
var style = document.createElement('style');
style.id = 'secubox-early-hide';
style.textContent = 'body[data-page^="admin-secubox"] ul.tabs:not(.sb-nav-tabs), body[data-page^="admin-secubox"] .tabs:not(.sb-nav-tabs) { display: none !important; }';
(document.head || document.documentElement).appendChild(style);
})();
var mainTabs = [
{ id: 'dashboard', icon: '📊', label: _('Dashboard'), path: ['admin', 'secubox', 'dashboard'] },
{ id: 'wizard', icon: '✨', label: _('Wizard'), path: ['admin', 'secubox', 'wizard'] },
{ id: 'modules', icon: '🧩', label: _('Modules'), path: ['admin', 'secubox', 'modules'] },
{ id: 'apps', icon: '🛒', label: _('App Store'), path: ['admin', 'secubox', 'apps'] },
{ id: 'monitoring', icon: '📡', label: _('Monitoring'), path: ['admin', 'secubox', 'monitoring'] },
{ id: 'alerts', icon: '⚠️', label: _('Alerts'), path: ['admin', 'secubox', 'alerts'] },
{ id: 'settings', icon: '⚙️', label: _('Settings'), path: ['admin', 'secubox', 'settings'] },
{ id: 'help', icon: '🎁', label: _('Bonus'), path: ['admin', 'secubox', 'help'] }
];
// Track initialization state
var _themeInitialized = false;
var _cssLoaded = false;
return baseclass.extend({
/**
* Get main SecuBox tabs
* @returns {Array} Copy of main tabs array
*/
getTabs: function() {
return mainTabs.slice();
},
/**
* Initialize theme and load CSS (idempotent)
* Called automatically by renderTabs/renderCompactTabs
*/
ensureThemeReady: function() {
if (_themeInitialized) return;
// Detect language
var lang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
// Initialize theme
Theme.init({ language: lang });
_themeInitialized = true;
},
/**
* Load SecuBox CSS files (idempotent)
*/
ensureCSSLoaded: function() {
if (_cssLoaded) return;
if (typeof document === 'undefined') return;
var cssFiles = [
'secubox-theme/secubox-theme.css',
'secubox-theme/themes/cyberpunk.css',
'secubox-theme/core/variables.css',
'secubox/common.css'
];
cssFiles.forEach(function(file) {
var id = 'secubox-css-' + file.replace(/[\/\.]/g, '-');
if (document.getElementById(id)) return;
var link = document.createElement('link');
link.id = id;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = L.resource(file);
document.head.appendChild(link);
});
_cssLoaded = true;
},
/**
* Hide default LuCI tabs
*/
ensureLuCITabsHidden: function() {
if (typeof document === 'undefined')
return;
// Actively remove LuCI tabs from DOM
var luciTabs = document.querySelectorAll('.cbi-tabmenu, ul.tabs, div.tabs, .nav-tabs');
luciTabs.forEach(function(el) {
// Don't remove our own tabs
if (!el.classList.contains('sb-nav-tabs') && !el.classList.contains('sh-nav-tabs')) {
el.style.display = 'none';
// Also try removing from DOM after a brief delay
setTimeout(function() {
if (el.parentNode && !el.classList.contains('sb-nav-tabs') && !el.classList.contains('sh-nav-tabs')) {
el.style.display = 'none';
}
}, 100);
}
});
if (document.getElementById('secubox-tabstyle'))
return;
var style = document.createElement('style');
style.id = 'secubox-tabstyle';
style.textContent = `
/* Hide default LuCI tabs for SecuBox - aggressive selectors */
/* Target any ul.tabs in the page */
ul.tabs {
display: none !important;
}
/* Be more specific for pages that need tabs elsewhere */
body:not([data-page^="admin-secubox"]):not([data-page*="cdn-cache"]):not([data-page*="network-modes"]) ul.tabs {
display: block !important;
}
/* All possible LuCI tab selectors */
body[data-page^="admin-secubox-dashboard"] .tabs,
body[data-page^="admin-secubox-modules"] .tabs,
body[data-page^="admin-secubox-wizard"] .tabs,
body[data-page^="admin-secubox-apps"] .tabs,
body[data-page^="admin-secubox-monitoring"] .tabs,
body[data-page^="admin-secubox-alerts"] .tabs,
body[data-page^="admin-secubox-settings"] .tabs,
body[data-page^="admin-secubox-help"] .tabs,
body[data-page^="admin-secubox"] #tabmenu,
body[data-page^="admin-secubox"] .cbi-tabmenu,
body[data-page^="admin-secubox"] .nav-tabs,
body[data-page^="admin-secubox"] ul.cbi-tabmenu,
body[data-page^="admin-secubox"] ul.tabs,
/* Fallback: hide any tabs that appear before our custom nav */
.secubox-dashboard .tabs,
.secubox-dashboard + .tabs,
.secubox-dashboard ~ .tabs,
.cbi-map > .tabs:first-child,
#maincontent > .container > .tabs,
#maincontent > .container > ul.tabs,
#view > .tabs,
#view > ul.tabs,
.view > .tabs,
.view > ul.tabs,
div.tabs:has(+ .secubox-dashboard),
/* Direct sibling of SecuBox content */
.sb-nav-tabs ~ .tabs,
.sh-nav-tabs ~ .tabs,
/* LuCI 24.x specific */
.luci-app-secubox .tabs,
#cbi-secubox .tabs {
display: none !important;
}
/* Hide tabs container when our nav is present */
.sb-nav-tabs ~ ul.tabs,
.sb-nav-tabs + ul.tabs,
.sh-nav-tabs ~ ul.tabs,
.sh-nav-tabs + ul.tabs {
display: none !important;
}
/* ==================== Main SecuBox Nav Tabs ==================== */
.sb-nav-tabs {
display: flex;
gap: 4px;
margin-bottom: 24px;
padding: 6px;
background: var(--sb-bg-secondary, var(--cyber-bg-secondary, #151932));
border-radius: var(--sb-radius-lg, 12px);
border: 1px solid var(--sb-border, var(--cyber-border-color, #2d2d5a));
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.sb-nav-tab {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border-radius: var(--sb-radius, 8px);
background: transparent;
border: none;
color: var(--sb-text-secondary, var(--cyber-text-secondary, #94a3b8));
font-weight: 500;
font-size: 13px;
cursor: pointer;
text-decoration: none;
transition: all 0.2s ease;
white-space: nowrap;
}
.sb-nav-tab:hover {
color: var(--sb-text-primary, var(--cyber-text-primary, #e2e8f0));
background: var(--sb-bg-tertiary, var(--cyber-bg-tertiary, #1e2139));
}
.sb-nav-tab.active {
color: var(--sb-accent, var(--cyber-accent-primary, #667eea));
background: var(--sb-bg-tertiary, var(--cyber-bg-tertiary, #1e2139));
box-shadow: inset 0 -2px 0 var(--sb-accent, var(--cyber-accent-primary, #667eea));
}
.sb-tab-icon {
font-size: 16px;
line-height: 1;
}
.sb-tab-label {
font-family: var(--sb-font-sans, system-ui, -apple-system, sans-serif);
}
/* ==================== Compact Nav Tabs (for nested modules) ==================== */
.sh-nav-tabs {
display: flex;
gap: 2px;
margin-bottom: 16px;
padding: 4px;
background: var(--sb-bg-secondary, var(--cyber-bg-secondary, #151932));
border-radius: var(--sb-radius, 8px);
border: 1px solid var(--sb-border, var(--cyber-border-color, #2d2d5a));
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.sh-nav-tab {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border-radius: 6px;
background: transparent;
border: none;
color: var(--sb-text-secondary, var(--cyber-text-secondary, #94a3b8));
font-weight: 500;
font-size: 12px;
cursor: pointer;
text-decoration: none;
transition: all 0.15s ease;
white-space: nowrap;
}
.sh-nav-tab:hover {
color: var(--sb-text-primary, var(--cyber-text-primary, #e2e8f0));
background: var(--sb-bg-tertiary, var(--cyber-bg-tertiary, #1e2139));
}
.sh-nav-tab.active {
color: #fff;
background: var(--sb-accent, var(--cyber-accent-primary, #667eea));
}
.sh-tab-icon {
font-size: 14px;
line-height: 1;
}
.sh-tab-label {
font-family: var(--sb-font-sans, system-ui, -apple-system, sans-serif);
}
/* ==================== Responsive ==================== */
@media (max-width: 768px) {
.sb-nav-tabs {
padding: 4px;
}
.sb-nav-tab {
padding: 8px 12px;
font-size: 12px;
}
.sb-tab-label {
display: none;
}
.sb-tab-icon {
font-size: 18px;
}
.sh-nav-tabs {
padding: 3px;
}
.sh-nav-tab {
padding: 6px 10px;
font-size: 11px;
}
.sh-tab-label {
display: none;
}
.sh-tab-icon {
font-size: 16px;
}
}
`;
document.head && document.head.appendChild(style);
},
/**
* Render main SecuBox navigation tabs
* Automatically initializes theme and loads CSS
*
* @param {String} active - ID of the active tab
* @returns {HTMLElement} Navigation element
*/
renderTabs: function(active) {
this.ensureThemeReady();
this.ensureCSSLoaded();
this.ensureLuCITabsHidden();
return E('div', { 'class': 'sb-nav-tabs' },
this.getTabs().map(function(tab) {
return E('a', {
'class': 'sb-nav-tab' + (tab.id === active ? ' active' : ''),
'href': L.url.apply(L, tab.path)
}, [
E('span', { 'class': 'sb-tab-icon' }, tab.icon),
E('span', { 'class': 'sb-tab-label' }, tab.label)
]);
})
);
},
/**
* Render compact navigation tabs for nested modules
* Use this for sub-module navigation (CDN Cache, Network Modes, etc.)
*
* @param {String} active - ID of the active tab
* @param {Array} tabs - Array of tab objects: { id, icon, label, path }
* @param {Object} options - Optional configuration
* @param {String} options.className - Additional CSS class for the container
* @returns {HTMLElement} Navigation element
*/
renderCompactTabs: function(active, tabs, options) {
var opts = options || {};
this.ensureThemeReady();
this.ensureCSSLoaded();
this.ensureLuCITabsHidden();
var className = 'sh-nav-tabs';
if (opts.className) {
className += ' ' + opts.className;
}
return E('div', { 'class': className },
(tabs || []).map(function(tab) {
return E('a', {
'class': 'sh-nav-tab' + (tab.id === active ? ' active' : ''),
'href': Array.isArray(tab.path) ? L.url.apply(L, tab.path) : tab.path
}, [
E('span', { 'class': 'sh-tab-icon' }, tab.icon || ''),
E('span', { 'class': 'sh-tab-label' }, tab.label || tab.id)
]);
})
);
},
/**
* Create a breadcrumb-style navigation back to SecuBox
* Useful for deeply nested module views
*
* @param {String} moduleName - Display name of the current module
* @param {String} moduleIcon - Emoji icon for the module
* @returns {HTMLElement} Breadcrumb element
*/
renderBreadcrumb: function(moduleName, moduleIcon) {
this.ensureThemeReady();
this.ensureCSSLoaded();
return E('div', {
'class': 'sh-breadcrumb',
'style': 'display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--sb-text-secondary, #94a3b8);'
}, [
E('a', {
'href': L.url('admin', 'secubox', 'dashboard'),
'style': 'color: var(--sb-accent, #667eea); text-decoration: none;'
}, '📊 SecuBox'),
E('span', { 'style': 'opacity: 0.5;' }, ''),
E('span', {}, [
moduleIcon ? E('span', { 'style': 'margin-right: 4px;' }, moduleIcon) : null,
moduleName
])
]);
}
});