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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-09 13:59:23 +01:00
parent b314cae528
commit e7c53cec1d
3 changed files with 447 additions and 0 deletions

View File

@ -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;
}

View File

@ -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');

View File

@ -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