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:
parent
498c613466
commit
73dabec337
@ -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;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user