feat(theme): Integrate kiss-theme.js into luci-theme-secubox

- Move kiss-theme.js from luci-app-secubox-portal to theme package
- Bump theme version to 0.4.8
- Prevents file conflict between packages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-23 17:40:12 +01:00
parent 379fe8e4fe
commit d5f2a0a319
4 changed files with 761 additions and 1 deletions

7
.gitignore vendored
View File

@ -29,3 +29,10 @@ openwrt# Large package files - should not be in git
package/secubox/secubox-app-bonus/root/www/secubox-feed/luci-app-secubox-bonus_*.ipk package/secubox/secubox-app-bonus/root/www/secubox-feed/luci-app-secubox-bonus_*.ipk
package/secubox/secubox-app-bonus/root/www/secubox-feed/secubox-app-bonus_*.ipk package/secubox/secubox-app-bonus/root/www/secubox-feed/secubox-app-bonus_*.ipk
__pycache__/ __pycache__/
secubox-tools/c3box-vm/output/
secubox-tools/c3box-vm/*.img
secubox-tools/c3box-vm/*.img.gz
*.qcow2
*.vdi
*.vmdk
*.ova

View File

@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-theme-secubox PKG_NAME:=luci-theme-secubox
PKG_VERSION:=0.4.7 PKG_VERSION:=0.4.8
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -0,0 +1,753 @@
'use strict';
'require baseclass';
/**
* 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 - organized by category with collapsible sections
// Items with `tabs` array show sub-navigation when active
nav: [
{ cat: 'Dashboard', icon: '📊', collapsed: false, items: [
{ icon: '🏠', name: 'Home', path: 'admin/secubox-home' },
{ icon: '📊', name: 'Dashboard', path: 'admin/secubox/dashboard' },
{ icon: '🖥️', name: 'System Hub', path: 'admin/secubox/system/system-hub' }
]},
{ cat: 'Security', icon: '🛡️', collapsed: false, items: [
{ icon: '🧙', name: 'InterceptoR', path: 'admin/secubox/interceptor', tabs: [
{ name: 'Overview', path: 'admin/secubox/interceptor/overview' },
{ name: 'Services', path: 'admin/secubox/interceptor/services' }
]},
{ icon: '🛡️', name: 'CrowdSec', path: 'admin/secubox/security/crowdsec', tabs: [
{ name: 'Overview', path: 'admin/secubox/security/crowdsec/overview' },
{ name: 'Decisions', path: 'admin/secubox/security/crowdsec/decisions' },
{ name: 'Alerts', path: 'admin/secubox/security/crowdsec/alerts' },
{ name: 'Bouncers', path: 'admin/secubox/security/crowdsec/bouncers' },
{ name: 'Setup', path: 'admin/secubox/security/crowdsec/setup' }
]},
{ icon: '🔍', name: 'mitmproxy', path: 'admin/secubox/security/mitmproxy', tabs: [
{ name: 'Status', path: 'admin/secubox/security/mitmproxy/status' },
{ name: 'Settings', path: 'admin/secubox/security/mitmproxy/settings' }
]},
{ icon: '🚫', name: 'Vortex FW', path: 'admin/secubox/security/vortex-firewall' },
{ icon: '👁️', name: 'Client Guard', path: 'admin/secubox/security/guardian', tabs: [
{ name: 'Clients', path: 'admin/secubox/security/guardian/clients' },
{ name: 'Settings', path: 'admin/secubox/security/guardian/settings' }
]},
{ icon: '🍪', name: 'Cookie Track', path: 'admin/secubox/interceptor/cookies' }
]},
{ cat: 'Network', icon: '🌐', collapsed: true, items: [
{ icon: '⚖️', name: 'HAProxy', path: 'admin/services/haproxy', tabs: [
{ name: 'Overview', path: 'admin/services/haproxy/overview' },
{ name: 'Vhosts', path: 'admin/services/haproxy/vhosts' },
{ name: 'Backends', path: 'admin/services/haproxy/backends' },
{ name: 'Certs', path: 'admin/services/haproxy/certificates' },
{ name: 'ACLs', path: 'admin/services/haproxy/acls' },
{ name: 'Stats', path: 'admin/services/haproxy/stats' },
{ name: 'Settings', path: 'admin/services/haproxy/settings' }
]},
{ icon: '🔒', name: 'WireGuard', path: 'admin/services/wireguard', tabs: [
{ name: 'Wizard', path: 'admin/services/wireguard/wizard' },
{ name: 'Overview', path: 'admin/services/wireguard/overview' },
{ name: 'Peers', path: 'admin/services/wireguard/peers' },
{ name: 'QR Codes', path: 'admin/services/wireguard/qrcodes' },
{ name: 'Traffic', path: 'admin/services/wireguard/traffic' },
{ name: 'Config', path: 'admin/services/wireguard/config' },
{ name: 'Settings', path: 'admin/services/wireguard/settings' }
]},
{ icon: '🌍', name: 'Tor Shield', path: 'admin/services/tor-shield', tabs: [
{ name: 'Overview', path: 'admin/services/tor-shield/overview' },
{ name: 'Circuits', path: 'admin/services/tor-shield/circuits' },
{ name: 'Hidden Svc', path: 'admin/services/tor-shield/hidden-services' },
{ name: 'Bridges', path: 'admin/services/tor-shield/bridges' },
{ name: 'Settings', path: 'admin/services/tor-shield/settings' }
]},
{ icon: '💾', name: 'CDN Cache', path: 'admin/services/cdn-cache', tabs: [
{ name: 'Overview', path: 'admin/services/cdn-cache/overview' },
{ name: 'Cache', path: 'admin/services/cdn-cache/cache' },
{ name: 'Policies', path: 'admin/services/cdn-cache/policies' },
{ name: 'Stats', path: 'admin/services/cdn-cache/statistics' },
{ name: 'Maint.', path: 'admin/services/cdn-cache/maintenance' },
{ name: 'Settings', path: 'admin/services/cdn-cache/settings' }
]},
{ icon: '📡', name: 'Bandwidth', path: 'admin/secubox/network/bandwidth-manager' },
{ icon: '📶', name: 'Traffic Shaper', path: 'admin/secubox/network/traffic-shaper', tabs: [
{ name: 'Overview', path: 'admin/secubox/network/traffic-shaper/overview' },
{ name: 'Classes', path: 'admin/secubox/network/traffic-shaper/classes' },
{ name: 'Rules', path: 'admin/secubox/network/traffic-shaper/rules' },
{ name: 'Stats', path: 'admin/secubox/network/traffic-shaper/stats' },
{ name: 'Presets', path: 'admin/secubox/network/traffic-shaper/presets' }
]},
{ icon: '🌐', name: 'Network Modes', path: 'admin/secubox/network/network-modes' },
{ icon: '🔌', name: 'Interfaces', path: 'admin/network/network' },
{ icon: '🌐', name: 'DNS Master', path: 'admin/services/dns-master' }
]},
{ cat: 'AI & LLM', icon: '🤖', collapsed: true, items: [
{ icon: '🧠', name: 'AI Insights', path: 'admin/secubox/ai/insights' },
{ icon: '🦙', name: 'Ollama', path: 'admin/services/ollama', tabs: [
{ name: 'Dashboard', path: 'admin/services/ollama/dashboard' },
{ name: 'Models', path: 'admin/services/ollama/models' },
{ name: 'Chat', path: 'admin/services/ollama/chat' },
{ name: 'Settings', path: 'admin/services/ollama/settings' }
]},
{ icon: '🤖', name: 'LocalAI', path: 'admin/services/localai' }
]},
{ cat: 'Apps', icon: '📦', collapsed: true, items: [
{ icon: '✉️', name: 'Mail Server', path: 'admin/services/mailserver' },
{ icon: '☁️', name: 'Nextcloud', path: 'admin/secubox/services/nextcloud', tabs: [
{ name: 'Overview', path: 'admin/secubox/services/nextcloud/overview' },
{ name: 'Settings', path: 'admin/secubox/services/nextcloud/settings' }
]},
{ icon: '🎬', name: 'Media Flow', path: 'admin/services/media-flow' },
{ icon: '🪞', name: 'MagicMirror', path: 'admin/services/magicmirror2' },
{ icon: '📰', name: 'HexoJS', path: 'admin/services/hexojs', tabs: [
{ name: 'Overview', path: 'admin/services/hexojs/overview' },
{ name: 'Posts', path: 'admin/services/hexojs/posts' },
{ name: 'Editor', path: 'admin/services/hexojs/editor' },
{ name: 'Media', path: 'admin/services/hexojs/media' },
{ name: 'Deploy', path: 'admin/services/hexojs/deploy' },
{ name: 'Sync', path: 'admin/services/hexojs/sync' },
{ name: 'Theme', path: 'admin/services/hexojs/theme' },
{ name: 'Settings', path: 'admin/services/hexojs/settings' }
]},
{ icon: '📺', name: 'Netdata', path: 'admin/services/netdata-dashboard' },
{ icon: '🏠', name: 'Vhost Manager', path: 'admin/services/vhost-manager' },
{ icon: '📦', name: 'App Store', path: 'admin/secubox/apps' }
]},
{ cat: 'Streamlit', icon: '🎯', collapsed: true, items: [
{ icon: '📺', name: 'France TV', url: 'http://192.168.255.1:8522/' },
{ icon: '🔮', name: 'Yijing Oracle', url: 'http://192.168.255.1:8501/' },
{ icon: '🏭', name: 'Fabricator', url: 'http://192.168.255.1:8520/' },
{ icon: '☯️', name: 'Bazi Complete', url: 'http://192.168.255.1:8509/' },
{ icon: '🎛️', name: 'SecuBox Control', url: 'http://192.168.255.1:8511/' }
]},
{ cat: 'P2P & Mesh', icon: '🔗', collapsed: true, items: [
{ icon: '🔗', name: 'P2P Network', path: 'admin/services/secubox-p2p' },
{ icon: '🌳', name: 'Netifyd', path: 'admin/services/secubox-netifyd' },
{ icon: '📡', name: 'Exposure', path: 'admin/services/exposure' }
]},
{ cat: 'System', icon: '⚙️', collapsed: true, items: [
{ icon: '⚙️', name: 'Settings', path: 'admin/system' },
{ icon: '📊', name: 'Status', path: 'admin/status/overview' },
{ icon: '🛠️', name: 'KSM Manager', path: 'admin/services/ksm-manager' },
{ icon: '🔄', name: 'Cloner', path: 'admin/secubox/system/cloner' },
{ icon: '🌳', name: 'LuCI Menu', path: 'admin/secubox/luci-tree' },
{ icon: '🔧', name: 'Software', path: 'admin/system/opkg' }
]}
],
// Track collapsed state per category
collapsedState: {},
// Core palette
colors: {
bg: '#0a0e17',
bg2: '#111827',
card: '#161e2e',
line: '#1e293b',
text: '#e2e8f0',
muted: '#94a3b8',
green: '#00C853',
red: '#FF1744',
blue: '#2979FF',
cyan: '#22d3ee',
purple: '#a78bfa',
orange: '#fb923c',
yellow: '#fbbf24'
},
// State
isKissMode: true,
sidebarOpen: false,
// CSS generation
generateCSS: function() {
var c = this.colors;
return `
/* 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};
--kiss-green: ${c.green}; --kiss-red: ${c.red}; --kiss-blue: ${c.blue};
--kiss-cyan: ${c.cyan}; --kiss-purple: ${c.purple}; --kiss-orange: ${c.orange};
--kiss-yellow: ${c.yellow};
}
/* === 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: 10px 16px 8px; font-size: 11px; letter-spacing: 0.5px;
text-transform: uppercase; color: ${c.muted}; font-weight: 600;
cursor: pointer; display: flex; align-items: center; gap: 8px;
transition: all 0.2s; border-radius: 6px; margin: 2px 8px;
}
.kiss-nav-section:hover { background: rgba(255,255,255,0.05); color: ${c.text}; }
.kiss-nav-section-icon { font-size: 14px; }
.kiss-nav-section-arrow { margin-left: auto; font-size: 10px; transition: transform 0.2s; }
.kiss-nav-section.collapsed .kiss-nav-section-arrow { transform: rotate(-90deg); }
.kiss-nav-section.collapsed + .kiss-nav-items { display: none; }
.kiss-nav-items { overflow: hidden; transition: all 0.2s; }
.kiss-nav-item {
display: flex; align-items: center; gap: 10px; padding: 8px 16px 8px 32px;
text-decoration: none; font-size: 12px; color: ${c.muted};
transition: all 0.2s; border-left: 3px solid transparent; margin: 1px 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-item.has-tabs { padding-right: 8px; }
.kiss-nav-item .tab-arrow { margin-left: auto; font-size: 9px; opacity: 0.5; transition: transform 0.2s; }
.kiss-nav-item.expanded .tab-arrow { transform: rotate(90deg); }
.kiss-nav-icon { font-size: 14px; width: 20px; text-align: center; flex-shrink: 0; }
/* === Sub-tabs (nested under active items) === */
.kiss-nav-tabs {
overflow: hidden; max-height: 0; transition: max-height 0.3s ease;
background: rgba(0,0,0,0.15);
}
.kiss-nav-tabs.expanded { max-height: 500px; }
.kiss-nav-tab {
display: flex; align-items: center; gap: 6px; padding: 6px 16px 6px 48px;
text-decoration: none; font-size: 11px; color: ${c.muted};
transition: all 0.15s; border-left: 2px solid transparent;
position: relative;
}
.kiss-nav-tab::before {
content: ''; position: absolute; left: 36px; top: 50%;
width: 4px; height: 4px; border-radius: 50%;
background: ${c.line}; transform: translateY(-50%);
}
.kiss-nav-tab:hover { background: rgba(255,255,255,0.03); color: ${c.text}; }
.kiss-nav-tab:hover::before { background: ${c.muted}; }
.kiss-nav-tab.active {
color: ${c.cyan}; background: rgba(34,211,238,0.05);
border-left-color: ${c.cyan};
}
.kiss-nav-tab.active::before { background: ${c.cyan}; }
/* === Main Content === */
.kiss-main {
margin-left: 220px; margin-top: 56px; padding: 20px;
min-height: calc(100vh - 56px); background: ${c.bg};
width: calc(100% - 220px); max-width: none;
}
.kiss-main > * { max-width: 100%; }
/* === 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; }
/* === Full Width Overrides === */
html, body { margin: 0; padding: 0; width: 100%; min-height: 100vh; background: ${c.bg}; }
body.kiss-mode { overflow-x: hidden; }
body.kiss-mode #maincontent,
body.kiss-mode .main-right,
body.kiss-mode .main {
margin: 0 !important; padding: 0 !important;
width: 100% !important; max-width: 100% !important;
background: ${c.bg} !important;
}
body.kiss-mode .container,
body.kiss-mode .cbi-map,
body.kiss-mode #cbi-network,
body.kiss-mode .cbi-section { max-width: 100% !important; width: 100% !important; }
/* === Cards === */
.kiss-root {
background: ${c.bg}; color: ${c.text}; font-family: 'Segoe UI', sans-serif;
width: 100%; min-height: 100vh; position: relative;
}
.kiss-root * { box-sizing: border-box; }
.kiss-card {
background: ${c.card}; border: 1px solid ${c.line};
border-radius: 12px; padding: 20px; margin-bottom: 16px;
}
.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 === */
.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); }
/* === Stats === */
.kiss-stat {
background: ${c.card}; border: 1px solid ${c.line};
border-radius: 10px; padding: 16px; text-align: center;
}
.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 ${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: ${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: ${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;
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 === */
.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, ${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 Buttons === */
.kiss-toggles {
position: fixed; bottom: 20px; right: 20px; z-index: 9999;
display: flex; flex-direction: column; gap: 8px;
}
.kiss-toggle-btn {
font-size: 20px; cursor: pointer; opacity: 0.7;
background: ${c.card}; border: 1px solid ${c.line};
border-radius: 50%; width: 44px; height: 44px;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.3); transition: all 0.2s;
}
.kiss-toggle-btn:hover { opacity: 1; transform: scale(1.1); border-color: ${c.green}; }
.kiss-toggle-btn.active { border-color: ${c.green}; background: rgba(0,200,83,0.15); }
/* === Minimized mode === */
.kiss-chrome-hidden .kiss-topbar,
.kiss-chrome-hidden .kiss-sidebar,
.kiss-chrome-hidden .kiss-overlay { display: none !important; }
.kiss-chrome-hidden .kiss-main {
margin-left: 0 !important; margin-top: 0 !important;
width: 100% !important; padding: 16px !important;
}
/* === Responsive === */
@media (max-width: 1024px) {
.kiss-sidebar { transform: translateX(-100%); }
.kiss-sidebar.open { transform: translateX(0); }
.kiss-main { margin-left: 0; width: 100%; }
.kiss-grid-3, .kiss-grid-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.kiss-grid-2 { grid-template-columns: 1fr; }
.kiss-topbar-breadcrumb { display: none; }
.kiss-stat-value { font-size: 22px; }
.kiss-main { padding: 12px; width: 100%; }
.kiss-card { padding: 14px; }
.kiss-table { font-size: 12px; }
.kiss-table th, .kiss-table td { padding: 8px 10px; }
}
@media (max-width: 480px) {
.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; }
.kiss-main { padding: 8px; }
.kiss-stat { padding: 10px; }
.kiss-stat-value { font-size: 18px; }
}
`;
},
// Apply theme
apply: function(options) {
options = options || {};
if (!document.querySelector('#kiss-theme-css')) {
var style = document.createElement('style');
style.id = 'kiss-theme-css';
style.textContent = this.generateCSS();
document.head.appendChild(style);
}
this.addToggle();
if (options.hideChrome !== false) {
this.hideChrome();
}
},
chromeVisible: true,
addToggle: function() {
var self = this;
if (document.querySelector('.kiss-toggles')) return;
var container = document.createElement('div');
container.className = 'kiss-toggles';
// Toggle KISS chrome (sidebar/topbar)
var chromeToggle = document.createElement('div');
chromeToggle.className = 'kiss-toggle-btn active';
chromeToggle.innerHTML = '📐';
chromeToggle.title = 'Toggle sidebar & top bar';
chromeToggle.onclick = function() { self.toggleChrome(); };
// Toggle LuCI mode
var modeToggle = document.createElement('div');
modeToggle.id = 'kiss-mode-toggle';
modeToggle.className = 'kiss-toggle-btn';
modeToggle.innerHTML = '👁️';
modeToggle.title = 'Switch to LuCI mode';
modeToggle.onclick = function() { self.toggleMode(); };
container.appendChild(chromeToggle);
container.appendChild(modeToggle);
document.body.appendChild(container);
},
toggleChrome: function() {
this.chromeVisible = !this.chromeVisible;
var btn = document.querySelector('.kiss-toggles .kiss-toggle-btn');
var root = document.querySelector('.kiss-root');
if (this.chromeVisible) {
btn.classList.add('active');
btn.innerHTML = '📐';
if (root) root.classList.remove('kiss-chrome-hidden');
} else {
btn.classList.remove('active');
btn.innerHTML = '📏';
if (root) root.classList.add('kiss-chrome-hidden');
}
},
toggleMode: function() {
this.isKissMode = !this.isKissMode;
var toggle = document.getElementById('kiss-mode-toggle');
if (this.isKissMode) {
toggle.innerHTML = '👁️';
toggle.title = 'Switch to LuCI mode';
this.hideChrome();
} else {
toggle.innerHTML = '👁️‍🗨️';
toggle.title = 'Switch to KISS mode';
this.showChrome();
}
},
showChrome: function() {
['#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');
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'; });
},
hideChrome: function() {
document.body.classList.add('kiss-mode');
// 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');
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');
},
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')
el.addEventListener(k.slice(2).toLowerCase(), attrs[k]);
else el.setAttribute(k, attrs[k]);
}
}
if (children) {
(Array.isArray(children) ? children : [children]).forEach(function(c) {
if (c == null) return;
el.appendChild(typeof c === 'string' ? document.createTextNode(c) : c);
});
}
return el;
},
card: function(title, content) {
return this.E('div', { 'class': 'kiss-card' }, [
title ? this.E('div', { 'class': 'kiss-card-title' }, title) : null,
this.E('div', {}, content)
]);
},
stat: function(value, label, color) {
return this.E('div', { 'class': 'kiss-stat' }, [
this.E('div', { 'class': 'kiss-stat-value', 'style': color ? 'color:' + color : '' }, String(value)),
this.E('div', { 'class': 'kiss-stat-label' }, label)
]);
},
badge: function(text, type) {
return this.E('span', { 'class': 'kiss-badge kiss-badge-' + (type || 'green') }, text);
},
btn: function(label, onClick, type) {
return this.E('button', { 'class': 'kiss-btn' + (type ? ' kiss-btn-' + type : ''), 'onClick': onClick }, label);
},
// 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')
])
])
]);
},
// Toggle category collapsed state
toggleCategory: function(catName) {
this.collapsedState[catName] = !this.collapsedState[catName];
var section = document.querySelector('.kiss-nav-section[data-cat="' + catName + '"]');
if (section) {
section.classList.toggle('collapsed', this.collapsedState[catName]);
}
},
// Render sidebar
renderSidebar: function(activePath) {
var self = this;
var currentPath = activePath || window.location.pathname.replace('/cgi-bin/luci/', '');
var navItems = [];
// Initialize collapsed state from nav config
this.nav.forEach(function(cat) {
if (self.collapsedState[cat.cat] === undefined) {
// Auto-expand if current path is in this category
var hasActive = cat.items.some(function(item) {
if (item.path && currentPath.indexOf(item.path) !== -1) return true;
if (item.tabs) {
return item.tabs.some(function(tab) {
return currentPath === tab.path || currentPath.indexOf(tab.path) !== -1;
});
}
return false;
});
self.collapsedState[cat.cat] = hasActive ? false : (cat.collapsed || false);
}
});
this.nav.forEach(function(cat) {
var isCollapsed = self.collapsedState[cat.cat];
// Section header (clickable to expand/collapse)
navItems.push(self.E('div', {
'class': 'kiss-nav-section' + (isCollapsed ? ' collapsed' : ''),
'data-cat': cat.cat,
'onClick': function(e) {
e.preventDefault();
self.toggleCategory(cat.cat);
}
}, [
self.E('span', { 'class': 'kiss-nav-section-icon' }, cat.icon || '📁'),
self.E('span', {}, cat.cat),
self.E('span', { 'class': 'kiss-nav-section-arrow' }, '▼')
]));
// Items container with sub-tabs
var itemElements = [];
cat.items.forEach(function(item) {
var isExternal = !!item.url;
var href = isExternal ? item.url : '/cgi-bin/luci/' + item.path;
var hasTabs = item.tabs && item.tabs.length > 0;
// Check if this item or any of its tabs is active
var isItemActive = !isExternal && item.path && currentPath.indexOf(item.path) !== -1;
var isTabActive = hasTabs && item.tabs.some(function(tab) {
return currentPath === tab.path || currentPath.indexOf(tab.path) !== -1;
});
var isActive = isItemActive || isTabActive;
// Main nav item
itemElements.push(self.E('a', {
'href': href,
'class': 'kiss-nav-item' + (isActive ? ' active' : '') + (isExternal ? ' external' : '') + (hasTabs ? ' has-tabs' : '') + (isActive && hasTabs ? ' expanded' : ''),
'target': isExternal ? '_blank' : '',
'onClick': function() { self.closeSidebar(); }
}, [
self.E('span', { 'class': 'kiss-nav-icon' }, item.icon),
self.E('span', {}, item.name),
isExternal ? self.E('span', { 'style': 'margin-left:auto;font-size:10px;opacity:0.5;' }, '↗') : null,
hasTabs ? self.E('span', { 'class': 'tab-arrow' }, '▶') : null
]));
// Sub-tabs (only rendered if item has tabs)
if (hasTabs) {
var tabsContainer = self.E('div', {
'class': 'kiss-nav-tabs' + (isActive ? ' expanded' : '')
}, item.tabs.map(function(tab) {
var tabActive = currentPath === tab.path || currentPath.indexOf(tab.path) !== -1;
return self.E('a', {
'href': '/cgi-bin/luci/' + tab.path,
'class': 'kiss-nav-tab' + (tabActive ? ' active' : ''),
'onClick': function() { self.closeSidebar(); }
}, tab.name);
}));
itemElements.push(tabsContainer);
}
});
var itemsContainer = self.E('div', { 'class': 'kiss-nav-items' }, itemElements);
navItems.push(itemsContainer);
});
return this.E('nav', { 'class': 'kiss-sidebar' }, [
this.E('div', { 'class': 'kiss-nav' }, navItems)
]);
},
// 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' }, [
this.renderTopbar(),
this.renderSidebar(activePath),
this.renderOverlay(),
this.E('div', { 'class': 'kiss-main' }, Array.isArray(content) ? content : [content])
]);
}
});
var KissTheme = new KissThemeClass();
window.KissTheme = KissTheme;
return KissThemeClass;