From 73dabec3372b5ffd7793be5837bc906d166df747 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 12 Feb 2026 06:33:41 +0100 Subject: [PATCH] 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 --- .../resources/secubox/kiss-theme.js | 456 ++++++++++-------- 1 file changed, 258 insertions(+), 198 deletions(-) diff --git a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox/kiss-theme.js b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox/kiss-theme.js index 2f081052..4350a7cd 100644 --- a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox/kiss-theme.js +++ b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/secubox/kiss-theme.js @@ -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;