From e7c53cec1d284fcf6e061cd63217c0defb8adfe1 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 9 Jan 2026 13:59:23 +0100 Subject: [PATCH] feat(theme): Add global SecuBox theming with app-specific accents (Phase 3) - Create app-themes.css with accent colors for all SecuBox apps: * CrowdSec: Security green (#00d4aa) * Bandwidth Manager: Blue (#3b82f6) * Client Guardian: Purple (#8b5cf6) * Media Flow: Pink (#ec4899) * Network Tools: Cyan (#06b6d4) * System Hub: Orange (#f97316) * Netdata: Green (#22c55e) * WireGuard: Red (#ef4444) * AppStore: Indigo (#6366f1) * CDN Cache: Teal (#14b8a6) * SecuBox Portal: Primary gradient (#667eea) - Enhance theme.js with shared UI components: * setApp() - Set app context for dynamic theming * renderNavTabs() - Create unified navigation tabs * createStatCard() - Stat cards with trend indicators * createMiniChart() - SVG sparkline charts * showToast() - Toast notification system - Add app-aware component styling in CSS: * Navigation tabs, buttons, cards use app accent * Form focus states, toggles, progress bars * Table headers, badges, tooltips * Loading spinners, chart colors Co-Authored-By: Claude Opus 4.5 --- .../secubox-theme/core/app-themes.css | 221 +++++++++++++++++ .../resources/secubox-theme/secubox-theme.css | 1 + .../resources/secubox-theme/theme.js | 225 ++++++++++++++++++ 3 files changed, 447 insertions(+) create mode 100644 luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/core/app-themes.css diff --git a/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/core/app-themes.css b/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/core/app-themes.css new file mode 100644 index 00000000..529f3489 --- /dev/null +++ b/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/core/app-themes.css @@ -0,0 +1,221 @@ +/** + * SecuBox App-Specific Theme Accents + * File: core/app-themes.css + * Version: 1.0.0 + * + * Provides app-specific accent colors while maintaining + * the core SecuBox design system consistency. + * + * Usage: Add data-secubox-app="appname" to body or container element + */ + +/* CrowdSec Dashboard - Security green */ +[data-secubox-app="crowdsec"], +.crowdsec-dashboard { + --sb-accent-primary: #00d4aa; + --sb-accent-glow: rgba(0, 212, 170, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #00d4aa 0%, #00a080 100%); + --sb-accent-soft: rgba(0, 212, 170, 0.15); +} + +/* Bandwidth Manager - Blue for network traffic */ +[data-secubox-app="bandwidth"], +.bandwidth-manager { + --sb-accent-primary: #3b82f6; + --sb-accent-glow: rgba(59, 130, 246, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + --sb-accent-soft: rgba(59, 130, 246, 0.15); +} + +/* Client Guardian - Purple for client protection */ +[data-secubox-app="guardian"], +.client-guardian { + --sb-accent-primary: #8b5cf6; + --sb-accent-glow: rgba(139, 92, 246, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); + --sb-accent-soft: rgba(139, 92, 246, 0.15); +} + +/* Media Flow - Pink for streaming/media */ +[data-secubox-app="media"], +.media-flow { + --sb-accent-primary: #ec4899; + --sb-accent-glow: rgba(236, 72, 153, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #ec4899 0%, #db2777 100%); + --sb-accent-soft: rgba(236, 72, 153, 0.15); +} + +/* Network Tools (nDPId, Netifyd) - Cyan for network analysis */ +[data-secubox-app="network"], +.ndpid-dashboard, +.netifyd-dashboard { + --sb-accent-primary: #06b6d4; + --sb-accent-glow: rgba(6, 182, 212, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%); + --sb-accent-soft: rgba(6, 182, 212, 0.15); +} + +/* System Hub - Orange for system administration */ +[data-secubox-app="system"], +.system-hub { + --sb-accent-primary: #f97316; + --sb-accent-glow: rgba(249, 115, 22, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #f97316 0%, #ea580c 100%); + --sb-accent-soft: rgba(249, 115, 22, 0.15); +} + +/* Netdata Dashboard - Green for monitoring */ +[data-secubox-app="netdata"], +.netdata-dashboard { + --sb-accent-primary: #22c55e; + --sb-accent-glow: rgba(34, 197, 94, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #22c55e 0%, #16a34a 100%); + --sb-accent-soft: rgba(34, 197, 94, 0.15); +} + +/* WireGuard - Red for VPN/security */ +[data-secubox-app="wireguard"], +.wireguard-dashboard { + --sb-accent-primary: #ef4444; + --sb-accent-glow: rgba(239, 68, 68, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); + --sb-accent-soft: rgba(239, 68, 68, 0.15); +} + +/* AppStore - Indigo for store/marketplace */ +[data-secubox-app="appstore"], +.secubox-appstore { + --sb-accent-primary: #6366f1; + --sb-accent-glow: rgba(99, 102, 241, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); + --sb-accent-soft: rgba(99, 102, 241, 0.15); +} + +/* CDN Cache - Teal for caching */ +[data-secubox-app="cdn"], +.cdn-cache { + --sb-accent-primary: #14b8a6; + --sb-accent-glow: rgba(20, 184, 166, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #14b8a6 0%, #0d9488 100%); + --sb-accent-soft: rgba(20, 184, 166, 0.15); +} + +/* Default/SecuBox Portal - Primary gradient */ +[data-secubox-app="portal"], +.secubox-portal { + --sb-accent-primary: #667eea; + --sb-accent-glow: rgba(102, 126, 234, 0.3); + --sb-accent-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --sb-accent-soft: rgba(102, 126, 234, 0.15); +} + +/* ============================================ + App-Aware Component Styling + ============================================ */ + +/* Navigation tabs use app accent */ +[data-secubox-app] .sb-nav-tabs .sb-nav-tab.active, +[data-secubox-app] .cs-nav-tabs .cs-nav-tab.active { + background: var(--sb-accent-gradient, var(--cyber-gradient-primary)); + border-color: var(--sb-accent-primary, var(--cyber-accent-primary)); + box-shadow: 0 4px 12px var(--sb-accent-glow, rgba(102, 126, 234, 0.3)); +} + +/* Buttons with app accent */ +[data-secubox-app] .sb-btn-primary, +[data-secubox-app] .cs-btn-primary { + background: var(--sb-accent-gradient, var(--cyber-gradient-primary)); + box-shadow: 0 4px 12px var(--sb-accent-glow, rgba(102, 126, 234, 0.3)); +} + +[data-secubox-app] .sb-btn-primary:hover, +[data-secubox-app] .cs-btn-primary:hover { + box-shadow: 0 6px 20px var(--sb-accent-glow, rgba(102, 126, 234, 0.4)); +} + +/* Cards with app accent border */ +[data-secubox-app] .sb-card-accent, +[data-secubox-app] .cs-card-accent { + border-left: 4px solid var(--sb-accent-primary, var(--cyber-accent-primary)); +} + +/* Stat cards with app accent */ +[data-secubox-app] .sb-stat-primary, +[data-secubox-app] .cs-stat-primary { + background: var(--sb-accent-soft, rgba(102, 126, 234, 0.15)); + border-color: var(--sb-accent-primary, var(--cyber-accent-primary)); +} + +/* Progress bars with app accent */ +[data-secubox-app] .sb-progress-bar, +[data-secubox-app] .cs-progress-bar { + background: var(--sb-accent-gradient, var(--cyber-gradient-primary)); +} + +/* Badges with app accent */ +[data-secubox-app] .sb-badge-primary, +[data-secubox-app] .cs-badge-primary { + background: var(--sb-accent-primary, var(--cyber-accent-primary)); +} + +/* Links with app accent */ +[data-secubox-app] a.sb-link, +[data-secubox-app] a.cs-link { + color: var(--sb-accent-primary, var(--cyber-accent-primary)); +} + +/* Glow effect utility */ +[data-secubox-app] .sb-glow { + box-shadow: 0 0 20px var(--sb-accent-glow, rgba(102, 126, 234, 0.3)); +} + +/* Page title with app accent underline */ +[data-secubox-app] .sb-page-title::after, +[data-secubox-app] .cs-page-title::after { + content: ''; + display: block; + width: 60px; + height: 3px; + background: var(--sb-accent-gradient, var(--cyber-gradient-primary)); + margin-top: 0.5em; + border-radius: 2px; +} + +/* Table header with app accent */ +[data-secubox-app] .sb-table th, +[data-secubox-app] .cs-table th { + border-bottom: 2px solid var(--sb-accent-primary, var(--cyber-accent-primary)); +} + +/* Form focus states with app accent */ +[data-secubox-app] input:focus, +[data-secubox-app] select:focus, +[data-secubox-app] textarea:focus { + border-color: var(--sb-accent-primary, var(--cyber-accent-primary)); + box-shadow: 0 0 0 3px var(--sb-accent-soft, rgba(102, 126, 234, 0.15)); +} + +/* Toggle switches with app accent */ +[data-secubox-app] .sb-toggle:checked, +[data-secubox-app] .cs-toggle:checked { + background: var(--sb-accent-primary, var(--cyber-accent-primary)); +} + +/* Tooltip with app accent */ +[data-secubox-app] .sb-tooltip::after { + background: var(--sb-accent-primary, var(--cyber-accent-primary)); +} + +/* Loading spinner with app accent */ +[data-secubox-app] .sb-spinner { + border-top-color: var(--sb-accent-primary, var(--cyber-accent-primary)); +} + +/* Chart colors (use app accent as primary) */ +[data-secubox-app] { + --chart-color-1: var(--sb-accent-primary, #667eea); + --chart-color-2: #22c55e; + --chart-color-3: #f59e0b; + --chart-color-4: #ef4444; + --chart-color-5: #8b5cf6; +} diff --git a/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/secubox-theme.css b/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/secubox-theme.css index 8fdcfa86..b9acc17a 100644 --- a/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/secubox-theme.css +++ b/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/secubox-theme.css @@ -1,4 +1,5 @@ @import url('./core/variables.css'); +@import url('./core/app-themes.css'); @import url('./core/reset.css'); @import url('./core/typography.css'); @import url('./core/animations.css'); diff --git a/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/theme.js b/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/theme.js index 0f93daba..adde182b 100644 --- a/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/theme.js +++ b/luci-theme-secubox/htdocs/luci-static/resources/secubox-theme/theme.js @@ -121,6 +121,231 @@ return baseclass.extend({ ]); }, + /** + * Set the current app context for theming + * @param {String} appName - App identifier (crowdsec, bandwidth, guardian, media, network, system, etc.) + */ + setApp: function(appName) { + if (document.body) { + document.body.setAttribute('data-secubox-app', appName); + } + if (document.documentElement) { + document.documentElement.setAttribute('data-secubox-app', appName); + } + }, + + /** + * Create navigation tabs for SecuBox apps + * @param {Array} tabs - Array of tab objects with {id, label, icon, path} + * @param {String} activeId - Currently active tab ID + * @param {Object} options - Optional configuration + * @returns {HTMLElement} + */ + renderNavTabs: function(tabs, activeId, options) { + var opts = options || {}; + var baseUrl = opts.baseUrl || ''; + var onTabClick = opts.onTabClick || null; + + var navTabs = E('div', { + 'class': 'sb-nav-tabs', + 'style': 'display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.5rem; padding: 0.5rem; background: var(--cyber-bg-secondary, #151932); border-radius: var(--cyber-radius-md, 12px);' + }); + + tabs.forEach(function(tab) { + var isActive = tab.id === activeId; + var tabEl = E('a', { + 'href': tab.path ? (baseUrl + tab.path) : '#', + 'class': 'sb-nav-tab' + (isActive ? ' active' : ''), + 'data-tab': tab.id, + 'style': [ + 'display: inline-flex', + 'align-items: center', + 'gap: 0.5rem', + 'padding: 0.75rem 1.25rem', + 'text-decoration: none', + 'color: ' + (isActive ? '#fff' : 'var(--cyber-text-secondary, #94a3b8)'), + 'background: ' + (isActive ? 'var(--sb-accent-gradient, var(--cyber-gradient-primary))' : 'transparent'), + 'border-radius: var(--cyber-radius-sm, 8px)', + 'font-size: 0.9rem', + 'font-weight: ' + (isActive ? '600' : '500'), + 'transition: all 0.2s ease', + 'cursor: pointer', + isActive ? 'box-shadow: 0 4px 12px var(--sb-accent-glow, rgba(102, 126, 234, 0.3))' : '' + ].join('; '), + 'click': onTabClick ? function(ev) { + ev.preventDefault(); + onTabClick(tab.id, tab); + } : null + }, [ + tab.icon ? E('span', { 'class': 'sb-nav-icon' }, tab.icon) : null, + E('span', { 'class': 'sb-nav-label' }, tab.label) + ]); + + // Add hover effect for non-active tabs + if (!isActive) { + tabEl.addEventListener('mouseenter', function() { + this.style.background = 'var(--cyber-bg-tertiary, #1e2139)'; + this.style.color = 'var(--cyber-text-primary, #e2e8f0)'; + }); + tabEl.addEventListener('mouseleave', function() { + this.style.background = 'transparent'; + this.style.color = 'var(--cyber-text-secondary, #94a3b8)'; + }); + } + + navTabs.appendChild(tabEl); + }); + + return navTabs; + }, + + /** + * Create a stat card component + * @param {Object} options - {value, label, icon, trend, color} + * @returns {HTMLElement} + */ + createStatCard: function(options) { + var opts = options || {}; + var trendIcon = ''; + var trendColor = ''; + + if (opts.trend) { + if (opts.trend > 0) { + trendIcon = String.fromCodePoint(0x2191); // ↑ + trendColor = 'var(--cyber-success, #10b981)'; + } else if (opts.trend < 0) { + trendIcon = String.fromCodePoint(0x2193); // ↓ + trendColor = 'var(--cyber-danger, #ef4444)'; + } + } + + return E('div', { + 'class': 'sb-stat-card', + 'style': [ + 'background: var(--cyber-bg-secondary, #151932)', + 'border: var(--cyber-border)', + 'border-radius: var(--cyber-radius-md, 12px)', + 'padding: 1.25rem', + 'display: flex', + 'flex-direction: column', + 'gap: 0.5rem' + ].join('; ') + }, [ + E('div', { 'style': 'display: flex; align-items: center; justify-content: space-between;' }, [ + opts.icon ? E('span', { + 'style': 'font-size: 1.5rem; opacity: 0.8;' + }, opts.icon) : null, + trendIcon ? E('span', { + 'style': 'font-size: 0.85rem; color: ' + trendColor + }, trendIcon + ' ' + Math.abs(opts.trend) + '%') : null + ]), + E('div', { + 'style': 'font-size: 2rem; font-weight: 700; color: ' + (opts.color || 'var(--sb-accent-primary, var(--cyber-accent-primary))') + ';' + }, String(opts.value !== undefined ? opts.value : 0)), + E('div', { + 'style': 'font-size: 0.85rem; color: var(--cyber-text-secondary, #94a3b8);' + }, opts.label || '') + ]); + }, + + /** + * Create a mini chart (sparkline) using SVG + * @param {Array} data - Array of numeric values + * @param {Object} options - {width, height, color, fill} + * @returns {HTMLElement} + */ + createMiniChart: function(data, options) { + var opts = options || {}; + var width = opts.width || 100; + var height = opts.height || 30; + var color = opts.color || 'var(--sb-accent-primary, #667eea)'; + var fill = opts.fill !== false; + + if (!data || data.length < 2) { + return E('div', { 'style': 'width: ' + width + 'px; height: ' + height + 'px;' }); + } + + var max = Math.max.apply(null, data); + var min = Math.min.apply(null, data); + var range = max - min || 1; + + var points = data.map(function(val, i) { + var x = (i / (data.length - 1)) * width; + var y = height - ((val - min) / range) * height; + return x + ',' + y; + }); + + var pathD = 'M ' + points.join(' L '); + var fillPath = fill ? pathD + ' L ' + width + ',' + height + ' L 0,' + height + ' Z' : ''; + + var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.setAttribute('width', width); + svg.setAttribute('height', height); + svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height); + svg.style.display = 'block'; + + if (fill) { + var fillEl = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + fillEl.setAttribute('d', fillPath); + fillEl.setAttribute('fill', color); + fillEl.setAttribute('fill-opacity', '0.2'); + svg.appendChild(fillEl); + } + + var lineEl = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + lineEl.setAttribute('d', pathD); + lineEl.setAttribute('stroke', color); + lineEl.setAttribute('stroke-width', '2'); + lineEl.setAttribute('fill', 'none'); + svg.appendChild(lineEl); + + return svg; + }, + + /** + * Show a toast notification + * @param {String} message - Message to display + * @param {String} type - Type: success, error, warning, info + * @param {Number} duration - Duration in ms (default 4000) + */ + showToast: function(message, type, duration) { + var colors = { + success: 'var(--cyber-success, #10b981)', + error: 'var(--cyber-danger, #ef4444)', + warning: 'var(--cyber-warning, #f59e0b)', + info: 'var(--cyber-info, #06b6d4)' + }; + + // Remove existing toast + var existing = document.querySelector('.sb-toast'); + if (existing) existing.remove(); + + var toast = E('div', { + 'class': 'sb-toast', + 'style': [ + 'position: fixed', + 'bottom: 20px', + 'right: 20px', + 'padding: 1rem 1.5rem', + 'background: var(--cyber-bg-secondary, #151932)', + 'border-left: 4px solid ' + (colors[type] || colors.info), + 'border-radius: var(--cyber-radius-sm, 8px)', + 'color: var(--cyber-text-primary, #e2e8f0)', + 'font-size: 0.9rem', + 'box-shadow: var(--cyber-shadow)', + 'z-index: var(--cyber-z-toast, 1200)', + 'animation: cyber-slide-in-right 0.3s ease-out' + ].join('; ') + }, message); + + document.body.appendChild(toast); + + setTimeout(function() { + toast.style.animation = 'cyber-fade-out 0.3s ease-out forwards'; + setTimeout(function() { toast.remove(); }, 300); + }, duration || 4000); + }, + /** * Animate page transitions * @param {HTMLElement} oldContent - Element being removed