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:
parent
b314cae528
commit
e7c53cec1d
@ -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;
|
||||
}
|
||||
@ -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');
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user