feat(theme): KISS Theme v2.0 with top bar and responsive sidebar

- Add fixed top bar with hamburger menu, logo, breadcrumb, logout
- Collapsible sidebar with scrolling for long menus
- Expanded navigation: Dashboard, Security, Services, Apps, System
- Preserve #tabmenu for internal view tab navigation
- Mobile overlay backdrop for sidebar
- Responsive breakpoints: 1024px, 768px, 480px
- Toggle button moved to bottom-right corner

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-12 06:33:41 +01:00
parent 498c613466
commit 73dabec337

View File

@ -2,39 +2,40 @@
'require baseclass';
/**
* SecuBox KISS Theme - Shared styling for all LuCI dashboards
*
* Usage in any LuCI view:
* 'require secubox/kiss-theme';
* return view.extend({
* render: function() {
* KissTheme.apply(); // Injects CSS and hides LuCI chrome
* return E('div', { 'class': 'kiss-root' }, [...]);
* }
* });
* SecuBox KISS Theme v2.0 - Shared styling for all LuCI dashboards
* Features: Top bar with logout, collapsible sidebar, responsive design, view tabs
*/
var KissThemeClass = baseclass.extend({
// Navigation config - shared across all views
// Navigation config - organized by category
nav: [
{ cat: 'Main', items: [
{ cat: 'Dashboard', items: [
{ icon: '🏠', name: 'Home', path: 'admin/secubox-home' },
{ icon: '📊', name: 'System Hub', path: 'admin/secubox/system/system-hub' }
{ icon: '📊', name: 'Dashboard', path: 'admin/secubox/dashboard' },
{ icon: '🖥️', name: 'System Hub', path: 'admin/secubox/system/system-hub' }
]},
{ cat: 'Security', items: [
{ icon: '🧙', name: 'InterceptoR', path: 'admin/secubox/interceptor/overview' },
{ icon: '🛡️', name: 'CrowdSec', path: 'admin/secubox/security/crowdsec/overview' },
{ icon: '🔍', name: 'mitmproxy', path: 'admin/secubox/security/mitmproxy/status' },
{ icon: '🌐', name: 'DNS Guard', path: 'admin/secubox/security/dnsguard' }
{ icon: '🧙', name: 'InterceptoR', path: 'admin/secubox/interceptor' },
{ icon: '🛡️', name: 'CrowdSec', path: 'admin/secubox/security/crowdsec' },
{ icon: '🔍', name: 'mitmproxy', path: 'admin/secubox/security/mitmproxy' },
{ icon: '🚫', name: 'Vortex FW', path: 'admin/secubox/security/vortex-firewall' },
{ icon: '👁️', name: 'Client Guard', path: 'admin/services/client-guardian' }
]},
{ cat: 'Services', items: [
{ icon: '📡', name: 'IoT Guard', path: 'admin/secubox/services/iot-guard' },
{ icon: '⚖️', name: 'HAProxy', path: 'admin/services/haproxy' },
{ icon: '🔒', name: 'WireGuard', path: 'admin/services/wireguard' },
{ icon: '💾', name: 'CDN Cache', path: 'admin/services/cdn-cache' },
{ icon: '🔗', name: 'HAProxy', path: 'admin/services/haproxy' },
{ icon: '🔒', name: 'WireGuard', path: 'admin/services/wireguard' }
{ icon: '🌐', name: 'Network', path: 'admin/network' },
{ icon: '📡', name: 'Bandwidth', path: 'admin/services/bandwidth-manager' }
]},
{ cat: 'Navigate', items: [
{ icon: '🌳', name: 'LuCI Tree', path: 'admin/secubox/luci-tree' }
{ cat: 'Apps', items: [
{ icon: '🎬', name: 'Media Flow', path: 'admin/services/media-flow' },
{ icon: '🤖', name: 'LocalAI', path: 'admin/services/localai' },
{ icon: '📦', name: 'App Store', path: 'admin/secubox/apps' }
]},
{ cat: 'System', items: [
{ icon: '⚙️', name: 'Settings', path: 'admin/system' },
{ icon: '🌳', name: 'LuCI Menu', path: 'admin/secubox/luci-tree' }
]}
],
@ -43,27 +44,27 @@ var KissThemeClass = baseclass.extend({
bg: '#0a0e17',
bg2: '#111827',
card: '#161e2e',
cardHover: '#1c2640',
line: '#1e293b',
text: '#e2e8f0',
muted: '#94a3b8',
green: '#00C853',
greenGlow: '#00E676',
red: '#FF1744',
blue: '#2979FF',
blueGlow: '#448AFF',
cyan: '#22d3ee',
purple: '#a78bfa',
orange: '#fb923c',
pink: '#f472b6',
yellow: '#fbbf24'
},
// State
isKissMode: true,
sidebarOpen: false,
// CSS generation
generateCSS: function() {
var c = this.colors;
return `
/* SecuBox KISS Theme */
/* SecuBox KISS Theme v2 */
:root {
--kiss-bg: ${c.bg}; --kiss-bg2: ${c.bg2}; --kiss-card: ${c.card};
--kiss-line: ${c.line}; --kiss-text: ${c.text}; --kiss-muted: ${c.muted};
@ -71,250 +72,286 @@ var KissThemeClass = baseclass.extend({
--kiss-cyan: ${c.cyan}; --kiss-purple: ${c.purple}; --kiss-orange: ${c.orange};
--kiss-yellow: ${c.yellow};
}
.kiss-root {
background: var(--kiss-bg); color: var(--kiss-text);
font-family: 'Outfit', 'Segoe UI', sans-serif;
min-height: 100vh; padding: 20px;
/* === Top Bar === */
.kiss-topbar {
position: fixed; top: 0; left: 0; right: 0; height: 56px; z-index: 1000;
background: linear-gradient(90deg, #0d1321 0%, ${c.bg} 100%);
border-bottom: 1px solid ${c.line};
display: flex; align-items: center; padding: 0 16px; gap: 12px;
}
.kiss-topbar-menu {
width: 40px; height: 40px; border: none; background: transparent;
color: ${c.text}; font-size: 24px; cursor: pointer; border-radius: 8px;
display: flex; align-items: center; justify-content: center;
}
.kiss-topbar-menu:hover { background: rgba(255,255,255,0.1); }
.kiss-topbar-logo {
font-weight: 900; font-size: 20px; display: flex; align-items: center; gap: 2px;
}
.kiss-topbar-logo .g { color: ${c.green}; }
.kiss-topbar-logo .r { color: ${c.red}; font-size: 12px; vertical-align: super; }
.kiss-topbar-logo .b { color: ${c.blue}; }
.kiss-topbar-logo .o { color: #546e7a; }
.kiss-topbar-breadcrumb {
flex: 1; font-size: 13px; color: ${c.muted}; overflow: hidden;
text-overflow: ellipsis; white-space: nowrap;
}
.kiss-topbar-breadcrumb a { color: ${c.cyan}; text-decoration: none; }
.kiss-topbar-actions { display: flex; gap: 8px; align-items: center; }
.kiss-topbar-btn {
padding: 8px 14px; border-radius: 6px; font-size: 12px; font-weight: 600;
border: 1px solid ${c.line}; background: ${c.bg2}; color: ${c.text};
cursor: pointer; display: flex; align-items: center; gap: 6px;
text-decoration: none;
}
.kiss-topbar-btn:hover { border-color: ${c.green}; background: rgba(0,200,83,0.1); }
.kiss-topbar-btn.logout { border-color: ${c.red}; color: ${c.red}; }
.kiss-topbar-btn.logout:hover { background: rgba(255,23,68,0.1); }
/* === Sidebar === */
.kiss-sidebar {
position: fixed; left: 0; top: 56px; bottom: 0; width: 220px;
background: linear-gradient(180deg, #0d1321 0%, ${c.bg} 100%);
border-right: 1px solid ${c.line}; z-index: 999;
overflow-y: auto; overflow-x: hidden;
transition: transform 0.3s ease;
}
.kiss-sidebar::-webkit-scrollbar { width: 4px; }
.kiss-sidebar::-webkit-scrollbar-thumb { background: ${c.line}; border-radius: 2px; }
.kiss-nav { padding: 8px 0; }
.kiss-nav-section {
padding: 12px 16px 6px; font-size: 10px; letter-spacing: 1.5px;
text-transform: uppercase; color: ${c.muted}; font-weight: 600;
}
.kiss-nav-item {
display: flex; align-items: center; gap: 10px; padding: 10px 16px;
text-decoration: none; font-size: 13px; color: ${c.muted};
transition: all 0.2s; border-left: 3px solid transparent; margin: 2px 0;
}
.kiss-nav-item:hover { background: rgba(255,255,255,0.05); color: ${c.text}; }
.kiss-nav-item.active {
color: ${c.green}; background: rgba(0,200,83,0.08);
border-left-color: ${c.green};
}
.kiss-nav-icon { font-size: 16px; width: 22px; text-align: center; flex-shrink: 0; }
/* === Main Content === */
.kiss-main {
margin-left: 220px; margin-top: 56px; padding: 20px;
min-height: calc(100vh - 56px); background: ${c.bg};
}
/* === View Tabs (internal navigation) === */
.kiss-tabs {
display: flex; gap: 4px; margin-bottom: 20px; padding-bottom: 12px;
border-bottom: 1px solid ${c.line}; flex-wrap: wrap;
}
.kiss-tab {
padding: 8px 16px; font-size: 13px; color: ${c.muted};
text-decoration: none; border-radius: 6px; transition: all 0.2s;
border: 1px solid transparent;
}
.kiss-tab:hover { color: ${c.text}; background: rgba(255,255,255,0.05); }
.kiss-tab.active {
color: ${c.green}; background: rgba(0,200,83,0.1);
border-color: rgba(0,200,83,0.3);
}
/* === Overlay for mobile === */
.kiss-overlay {
position: fixed; top: 56px; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6); z-index: 998; display: none;
}
.kiss-overlay.active { display: block; }
/* === Cards === */
.kiss-root { background: ${c.bg}; color: ${c.text}; font-family: 'Segoe UI', sans-serif; }
.kiss-root * { box-sizing: border-box; }
/* Cards */
.kiss-card {
background: var(--kiss-card); border: 1px solid var(--kiss-line);
background: ${c.card}; border: 1px solid ${c.line};
border-radius: 12px; padding: 20px; margin-bottom: 16px;
transition: all 0.2s;
}
.kiss-card:hover { border-color: rgba(0,200,83,0.2); }
.kiss-card-title {
font-weight: 700; font-size: 16px; margin-bottom: 12px;
display: flex; align-items: center; gap: 8px;
}
/* Grid */
/* === Grid === */
.kiss-grid { display: grid; gap: 16px; }
.kiss-grid-2 { grid-template-columns: repeat(2, 1fr); }
.kiss-grid-3 { grid-template-columns: repeat(3, 1fr); }
.kiss-grid-4 { grid-template-columns: repeat(4, 1fr); }
.kiss-grid-auto { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
/* Stats */
/* === Stats === */
.kiss-stat {
background: var(--kiss-card); border: 1px solid var(--kiss-line);
background: ${c.card}; border: 1px solid ${c.line};
border-radius: 10px; padding: 16px; text-align: center;
}
.kiss-stat-value {
font-family: 'Orbitron', monospace; font-weight: 700;
font-size: 28px; color: var(--kiss-green);
}
.kiss-stat-label {
font-size: 11px; color: var(--kiss-muted); text-transform: uppercase;
letter-spacing: 1px; margin-top: 4px;
}
/* Buttons */
.kiss-stat-value { font-family: monospace; font-weight: 700; font-size: 28px; color: ${c.green}; }
.kiss-stat-label { font-size: 11px; color: ${c.muted}; text-transform: uppercase; margin-top: 4px; }
/* === Buttons === */
.kiss-btn {
padding: 10px 18px; border-radius: 8px; font-size: 13px; font-weight: 600;
cursor: pointer; border: 1px solid var(--kiss-line); background: var(--kiss-bg2);
color: var(--kiss-text); transition: all 0.2s; display: inline-flex;
align-items: center; gap: 6px; text-decoration: none;
cursor: pointer; border: 1px solid ${c.line}; background: ${c.bg2};
color: ${c.text}; display: inline-flex; align-items: center; gap: 6px;
}
.kiss-btn:hover { border-color: rgba(0,200,83,0.3); background: rgba(0,200,83,0.05); }
.kiss-btn-green { border-color: var(--kiss-green); color: var(--kiss-green); }
.kiss-btn-red { border-color: var(--kiss-red); color: var(--kiss-red); }
.kiss-btn-blue { border-color: var(--kiss-blue); color: var(--kiss-blue); }
/* Status badges */
.kiss-btn-green { border-color: ${c.green}; color: ${c.green}; }
.kiss-btn-red { border-color: ${c.red}; color: ${c.red}; }
.kiss-btn-blue { border-color: ${c.blue}; color: ${c.blue}; }
/* === Badges === */
.kiss-badge {
font-family: monospace; font-size: 10px; letter-spacing: 1px;
padding: 4px 10px; border-radius: 4px; display: inline-block;
}
.kiss-badge-green { color: var(--kiss-green); background: rgba(0,200,83,0.1); border: 1px solid rgba(0,200,83,0.25); }
.kiss-badge-red { color: var(--kiss-red); background: rgba(255,23,68,0.1); border: 1px solid rgba(255,23,68,0.25); }
.kiss-badge-blue { color: var(--kiss-blue); background: rgba(41,121,255,0.1); border: 1px solid rgba(41,121,255,0.25); }
.kiss-badge-yellow { color: var(--kiss-yellow); background: rgba(251,191,36,0.1); border: 1px solid rgba(251,191,36,0.25); }
/* Tables */
.kiss-badge-green { color: ${c.green}; background: rgba(0,200,83,0.1); border: 1px solid rgba(0,200,83,0.25); }
.kiss-badge-red { color: ${c.red}; background: rgba(255,23,68,0.1); border: 1px solid rgba(255,23,68,0.25); }
.kiss-badge-blue { color: ${c.blue}; background: rgba(41,121,255,0.1); border: 1px solid rgba(41,121,255,0.25); }
.kiss-badge-yellow { color: ${c.yellow}; background: rgba(251,191,36,0.1); border: 1px solid rgba(251,191,36,0.25); }
/* === Tables === */
.kiss-table { width: 100%; border-collapse: separate; border-spacing: 0; }
.kiss-table th {
text-align: left; padding: 10px 16px; font-size: 11px; letter-spacing: 1px;
text-transform: uppercase; color: var(--kiss-muted); font-family: monospace;
border-bottom: 1px solid var(--kiss-line);
text-align: left; padding: 10px 16px; font-size: 11px;
text-transform: uppercase; color: ${c.muted}; border-bottom: 1px solid ${c.line};
}
.kiss-table td { padding: 10px 16px; font-size: 14px; border-bottom: 1px solid rgba(255,255,255,0.03); }
.kiss-table tr:hover td { background: rgba(255,255,255,0.02); }
/* Progress bars */
/* === Progress === */
.kiss-progress { height: 8px; background: rgba(255,255,255,0.06); border-radius: 4px; overflow: hidden; }
.kiss-progress-fill { height: 100%; border-radius: 4px; background: linear-gradient(90deg, var(--kiss-green), var(--kiss-cyan)); }
/* Panels with border accent */
.kiss-panel-green { border-left: 3px solid var(--kiss-green); }
.kiss-panel-red { border-left: 3px solid var(--kiss-red); }
.kiss-panel-blue { border-left: 3px solid var(--kiss-blue); }
.kiss-panel-orange { border-left: 3px solid var(--kiss-orange); }
/* Sidebar layout */
.kiss-with-sidebar { display: flex; }
.kiss-sidebar {
position: fixed; left: 0; top: 0; bottom: 0; width: 200px;
background: linear-gradient(180deg, #0d1321 0%, var(--kiss-bg) 100%);
border-right: 1px solid var(--kiss-line); z-index: 100;
display: flex; flex-direction: column; overflow-y: auto;
}
.kiss-sidebar-logo {
padding: 16px; border-bottom: 1px solid var(--kiss-line); text-align: center;
}
.kiss-logo-text { font-weight: 900; font-size: 24px; letter-spacing: -1px; }
.kiss-logo-sub { font-size: 9px; color: var(--kiss-muted); letter-spacing: 2px; margin-top: 4px; }
.kiss-nav { flex: 1; padding: 8px 0; }
.kiss-nav-section {
padding: 8px 12px 4px; font-size: 9px; letter-spacing: 2px;
text-transform: uppercase; color: var(--kiss-muted);
}
.kiss-nav-item {
display: flex; align-items: center; gap: 10px; padding: 8px 16px;
text-decoration: none; font-size: 13px; color: var(--kiss-muted);
transition: all 0.2s; border-left: 2px solid transparent;
}
.kiss-nav-item:hover { background: rgba(255,255,255,0.03); color: var(--kiss-text); }
.kiss-nav-item.active { color: var(--kiss-green); background: rgba(0,200,83,0.05); border-left-color: var(--kiss-green); }
.kiss-nav-icon { font-size: 16px; width: 20px; text-align: center; }
.kiss-main { margin-left: 200px; padding: 20px; flex: 1; min-height: 100vh; }
/* Toggle */
.kiss-progress-fill { height: 100%; border-radius: 4px; background: linear-gradient(90deg, ${c.green}, ${c.cyan}); }
/* === Panels === */
.kiss-panel-green { border-left: 3px solid ${c.green}; }
.kiss-panel-red { border-left: 3px solid ${c.red}; }
.kiss-panel-blue { border-left: 3px solid ${c.blue}; }
/* === Toggle Button === */
#kiss-toggle {
position: fixed; top: 10px; right: 10px; z-index: 99999;
font-size: 32px; cursor: pointer; opacity: 0.7; transition: all 0.3s;
background: rgba(0,0,0,0.5); border-radius: 50%; width: 50px; height: 50px;
position: fixed; bottom: 20px; right: 20px; z-index: 9999;
font-size: 24px; cursor: pointer; opacity: 0.7;
background: ${c.card}; border: 1px solid ${c.line};
border-radius: 50%; width: 48px; height: 48px;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
#kiss-toggle:hover { opacity: 1; transform: scale(1.1); }
/* Responsive */
/* === Responsive === */
@media (max-width: 1024px) {
.kiss-sidebar { transform: translateX(-100%); }
.kiss-sidebar.open { transform: translateX(0); }
.kiss-main { margin-left: 0; }
.kiss-grid-3, .kiss-grid-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.kiss-grid-2, .kiss-grid-3, .kiss-grid-4 { grid-template-columns: 1fr; }
.kiss-sidebar { width: 60px; }
.kiss-sidebar-logo, .kiss-nav-section { display: none; }
.kiss-nav-item { padding: 12px; justify-content: center; }
.kiss-nav-item span:last-child { display: none; }
.kiss-main { margin-left: 60px; }
.kiss-grid-2 { grid-template-columns: 1fr; }
.kiss-topbar-breadcrumb { display: none; }
.kiss-stat-value { font-size: 22px; }
.kiss-main { padding: 12px; }
}
@media (max-width: 480px) {
.kiss-sidebar { display: none; }
.kiss-main { margin-left: 0; }
.kiss-topbar-logo span:not(.g) { display: none; }
.kiss-topbar-btn span { display: none; }
.kiss-tabs { gap: 2px; }
.kiss-tab { padding: 6px 10px; font-size: 12px; }
}
`;
},
// State
isKissMode: true,
// Apply theme to page
// Apply theme
apply: function(options) {
options = options || {};
// Inject CSS if not already done
if (!document.querySelector('#kiss-theme-css')) {
var style = document.createElement('style');
style.id = 'kiss-theme-css';
style.textContent = this.generateCSS();
document.head.appendChild(style);
}
// Add toggle button
this.addToggle();
// Hide LuCI chrome if requested (default: true)
if (options.hideChrome !== false) {
this.hideChrome();
}
},
// Add eye toggle button
addToggle: function() {
var self = this;
if (document.querySelector('#kiss-toggle')) return;
var toggle = document.createElement('div');
toggle.id = 'kiss-toggle';
toggle.innerHTML = '👁️';
toggle.title = 'Toggle KISS/LuCI mode';
toggle.style.cssText = 'position:fixed;top:10px;right:10px;z-index:99999;' +
'font-size:32px;cursor:pointer;opacity:0.7;transition:all 0.3s;' +
'background:rgba(0,0,0,0.5);border-radius:50%;width:50px;height:50px;' +
'display:flex;align-items:center;justify-content:center;';
toggle.onmouseover = function() { this.style.opacity = '1'; this.style.transform = 'scale(1.1)'; };
toggle.onmouseout = function() { this.style.opacity = '0.7'; this.style.transform = 'scale(1)'; };
toggle.onclick = function() { self.toggleMode(); };
document.body.appendChild(toggle);
},
// Toggle between KISS and LuCI mode
toggleMode: function() {
this.isKissMode = !this.isKissMode;
var toggle = document.getElementById('kiss-toggle');
if (this.isKissMode) {
// KISS mode - hide chrome
toggle.innerHTML = '👁️';
this.hideChrome();
document.body.classList.add('kiss-mode');
} else {
// LuCI mode - show chrome
toggle.innerHTML = '👁️‍🗨️';
this.showChrome();
document.body.classList.remove('kiss-mode');
}
},
// Show LuCI chrome
showChrome: function() {
[
'#mainmenu', '.main-left', 'header.main-header', 'header',
'nav[role="navigation"]', 'aside', 'footer', '.container > header',
'.pull-right', '#indicators', '.brand', '#topmenu', '#tabmenu'
].forEach(function(sel) {
var el = document.querySelector(sel);
['#mainmenu', '.main-left', 'header', 'nav', 'aside', 'footer', '#topmenu', '#tabmenu', '.pull-right'].forEach(function(s) {
var el = document.querySelector(s);
if (el) el.style.display = '';
});
var main = document.querySelector('.main-right') || document.querySelector('#maincontent') || document.querySelector('.container');
if (main) {
main.style.marginLeft = '';
main.style.marginTop = '';
main.style.width = '';
main.style.padding = '';
main.style.maxWidth = '';
}
document.body.style.padding = '';
document.body.style.margin = '';
var main = document.querySelector('.main-right') || document.querySelector('#maincontent');
if (main) { main.style.marginLeft = ''; main.style.marginTop = ''; main.style.width = ''; }
// Hide KISS elements
var kissEl = document.querySelectorAll('.kiss-topbar, .kiss-sidebar, .kiss-overlay');
kissEl.forEach(function(el) { el.style.display = 'none'; });
},
// Hide LuCI navigation chrome
hideChrome: function() {
document.body.classList.add('kiss-mode');
[
'#mainmenu', '.main-left', 'header.main-header', 'header',
'nav[role="navigation"]', 'aside', 'footer', '.container > header',
'.pull-right', '#indicators', '.brand', '#topmenu', '#tabmenu'
].forEach(function(sel) {
var el = document.querySelector(sel);
// Hide LuCI chrome but KEEP #tabmenu for view tabs
['#mainmenu', '.main-left', 'header', 'nav[role="navigation"]', 'aside', 'footer', '#topmenu', '.pull-right'].forEach(function(s) {
var el = document.querySelector(s);
if (el) el.style.display = 'none';
});
var main = document.querySelector('.main-right') || document.querySelector('#maincontent') || document.querySelector('.container');
if (main) {
main.style.marginLeft = '0';
main.style.marginTop = '0';
main.style.width = '100%';
main.style.padding = '0';
main.style.maxWidth = 'none';
}
document.body.style.padding = '0';
document.body.style.margin = '0';
var main = document.querySelector('.main-right') || document.querySelector('#maincontent');
if (main) { main.style.marginLeft = '0'; main.style.marginTop = '0'; main.style.width = '100%'; }
// Show KISS elements
var kissEl = document.querySelectorAll('.kiss-topbar, .kiss-sidebar, .kiss-overlay');
kissEl.forEach(function(el) { el.style.display = ''; });
},
toggleSidebar: function() {
this.sidebarOpen = !this.sidebarOpen;
var sidebar = document.querySelector('.kiss-sidebar');
var overlay = document.querySelector('.kiss-overlay');
if (sidebar) sidebar.classList.toggle('open', this.sidebarOpen);
if (overlay) overlay.classList.toggle('active', this.sidebarOpen);
},
closeSidebar: function() {
this.sidebarOpen = false;
var sidebar = document.querySelector('.kiss-sidebar');
var overlay = document.querySelector('.kiss-overlay');
if (sidebar) sidebar.classList.remove('open');
if (overlay) overlay.classList.remove('active');
},
// Helper: Create element with KISS classes
E: function(tag, attrs, children) {
var el = document.createElement(tag);
if (attrs) {
for (var k in attrs) {
if (k === 'class') {
el.className = attrs[k];
} else if (k.startsWith('on') && typeof attrs[k] === 'function') {
if (k === 'class') el.className = attrs[k];
else if (k.startsWith('on') && typeof attrs[k] === 'function')
el.addEventListener(k.slice(2).toLowerCase(), attrs[k]);
} else {
el.setAttribute(k, attrs[k]);
}
else el.setAttribute(k, attrs[k]);
}
}
if (children) {
@ -326,7 +363,6 @@ var KissThemeClass = baseclass.extend({
return el;
},
// Component helpers
card: function(title, content) {
return this.E('div', { 'class': 'kiss-card' }, [
title ? this.E('div', { 'class': 'kiss-card-title' }, title) : null,
@ -335,9 +371,8 @@ var KissThemeClass = baseclass.extend({
},
stat: function(value, label, color) {
var style = color ? 'color:' + color : '';
return this.E('div', { 'class': 'kiss-stat' }, [
this.E('div', { 'class': 'kiss-stat-value', 'style': style }, String(value)),
this.E('div', { 'class': 'kiss-stat-value', 'style': color ? 'color:' + color : '' }, String(value)),
this.E('div', { 'class': 'kiss-stat-label' }, label)
]);
},
@ -347,13 +382,40 @@ var KissThemeClass = baseclass.extend({
},
btn: function(label, onClick, type) {
return this.E('button', {
'class': 'kiss-btn' + (type ? ' kiss-btn-' + type : ''),
'onClick': onClick
}, label);
return this.E('button', { 'class': 'kiss-btn' + (type ? ' kiss-btn-' + type : ''), 'onClick': onClick }, label);
},
// Render sidebar navigation
// Render top bar
renderTopbar: function() {
var self = this;
var path = window.location.pathname.replace('/cgi-bin/luci/', '').split('/');
var breadcrumb = path.map(function(p, i) {
var href = '/cgi-bin/luci/' + path.slice(0, i + 1).join('/');
return self.E('a', { 'href': href }, p);
});
return this.E('header', { 'class': 'kiss-topbar' }, [
this.E('button', { 'class': 'kiss-topbar-menu', 'onClick': function() { self.toggleSidebar(); } }, '☰'),
this.E('div', { 'class': 'kiss-topbar-logo' }, [
this.E('span', { 'class': 'g' }, 'C'),
this.E('span', { 'class': 'r' }, '3'),
this.E('span', { 'class': 'b' }, 'B'),
this.E('span', { 'class': 'o' }, 'O'),
this.E('span', { 'class': 'g' }, 'X')
]),
this.E('div', { 'class': 'kiss-topbar-breadcrumb' }, breadcrumb),
this.E('div', { 'class': 'kiss-topbar-actions' }, [
this.E('a', { 'class': 'kiss-topbar-btn', 'href': '/cgi-bin/luci/admin/system' }, [
'⚙️', this.E('span', {}, 'Settings')
]),
this.E('a', { 'class': 'kiss-topbar-btn logout', 'href': '/cgi-bin/luci/admin/logout' }, [
'🚪', this.E('span', {}, 'Logout')
])
])
]);
},
// Render sidebar
renderSidebar: function(activePath) {
var self = this;
var currentPath = activePath || window.location.pathname.replace('/cgi-bin/luci/', '');
@ -365,7 +427,8 @@ var KissThemeClass = baseclass.extend({
var isActive = currentPath.indexOf(item.path) !== -1;
navItems.push(self.E('a', {
'href': '/cgi-bin/luci/' + item.path,
'class': 'kiss-nav-item' + (isActive ? ' active' : '')
'class': 'kiss-nav-item' + (isActive ? ' active' : ''),
'onClick': function() { self.closeSidebar(); }
}, [
self.E('span', { 'class': 'kiss-nav-icon' }, item.icon),
self.E('span', {}, item.name)
@ -374,31 +437,28 @@ var KissThemeClass = baseclass.extend({
});
return this.E('nav', { 'class': 'kiss-sidebar' }, [
this.E('div', { 'class': 'kiss-sidebar-logo' }, [
this.E('div', { 'class': 'kiss-logo-text' }, [
this.E('span', { 'style': 'color: var(--kiss-green);' }, 'C'),
this.E('span', { 'style': 'color: var(--kiss-red); font-size: 14px; vertical-align: super;' }, '3'),
this.E('span', { 'style': 'color: var(--kiss-blue);' }, 'B'),
this.E('span', { 'style': 'color: #37474F;' }, 'O'),
this.E('span', { 'style': 'color: var(--kiss-green);' }, 'X')
]),
this.E('div', { 'class': 'kiss-logo-sub' }, 'SECUBOX')
]),
this.E('div', { 'class': 'kiss-nav' }, navItems)
]);
},
// Create full page with sidebar
// Render overlay for mobile
renderOverlay: function() {
var self = this;
return this.E('div', { 'class': 'kiss-overlay', 'onClick': function() { self.closeSidebar(); } });
},
// Wrap content with theme
wrap: function(content, activePath) {
this.apply();
return this.E('div', { 'class': 'kiss-root kiss-with-sidebar' }, [
return this.E('div', { 'class': 'kiss-root' }, [
this.renderTopbar(),
this.renderSidebar(activePath),
this.renderOverlay(),
this.E('div', { 'class': 'kiss-main' }, Array.isArray(content) ? content : [content])
]);
}
});
// Create singleton instance and expose globally for convenience
var KissTheme = new KissThemeClass();
window.KissTheme = KissTheme;