Migrated three core modules to use the global secubox-theme package:
Modules Updated:
- luci-app-secubox (dashboard, modules views)
- luci-app-network-modes (overview view)
- luci-app-system-hub (overview, services views)
Changes Per Module:
- Replaced module-specific theme imports with 'require secubox-theme/theme as Theme'
- Updated CSS imports to use global secubox-theme.css bundle
- Initialized global theme with Theme.init({ theme: 'dark', language: 'en' })
- Removed redundant theme.getTheme() calls from load() functions
- Added global theme CSS link tags to view renders
Benefits:
- Unified CyberMood design system across all modules
- Access to 100+ CSS variables (colors, spacing, effects)
- Theme switching support (dark, light, cyberpunk)
- Multi-language support (en, fr, de, es) via Theme.t()
- Reduced CSS duplication
- Consistent UI components (cards, buttons, badges)
Deployment:
- Created deploy-modules-with-theme.sh for batch deployment
- All modules successfully deployed to router 192.168.8.191
- Verified HTTP access to updated JavaScript files
Testing:
- ✅ SecuBox dashboard loads with global theme
- ✅ Network-modes overview uses theme CSS
- ✅ System-hub views integrate theme properly
- ✅ All 27 view files deployed successfully
Next Steps:
- Modules can now use Theme.createCard(), createButton(), createBadge()
- Translation keys available for internationalization
- Theme variants switchable via Theme.apply('dark'|'light'|'cyberpunk')
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
644 lines
20 KiB
JavaScript
644 lines
20 KiB
JavaScript
'use strict';
|
||
'require view';
|
||
'require ui';
|
||
'require secubox/api as API';
|
||
'require secubox-theme/theme as Theme';
|
||
'require dom';
|
||
'require poll';
|
||
|
||
// Load global theme CSS
|
||
document.head.appendChild(E('link', {
|
||
'rel': 'stylesheet',
|
||
'type': 'text/css',
|
||
'href': L.resource('secubox-theme/secubox-theme.css')
|
||
}));
|
||
|
||
// Initialize global theme
|
||
Theme.init({ theme: 'dark', language: 'en' });
|
||
|
||
return view.extend({
|
||
dashboardData: null,
|
||
healthData: null,
|
||
alertsData: null,
|
||
activeFilter: 'all',
|
||
|
||
load: function() {
|
||
return this.refreshData();
|
||
},
|
||
|
||
refreshData: function() {
|
||
var self = this;
|
||
return Promise.all([
|
||
API.getDashboardData(),
|
||
API.getSystemHealth(),
|
||
API.getAlerts()
|
||
]).then(function(data) {
|
||
self.dashboardData = data[0] || {};
|
||
self.healthData = data[1] || {};
|
||
self.alertsData = data[2] || {};
|
||
return data;
|
||
});
|
||
},
|
||
|
||
render: function(data) {
|
||
var self = this;
|
||
var container = E('div', { 'class': 'secubox-dashboard' });
|
||
|
||
// Header with version
|
||
var status = this.dashboardData.status || {};
|
||
container.appendChild(this.renderHeader(status));
|
||
|
||
// Stats Overview Cards
|
||
container.appendChild(this.renderStatsOverview());
|
||
|
||
// Main Content Grid
|
||
var mainGrid = E('div', { 'class': 'secubox-main-grid' }, [
|
||
// Left column
|
||
E('div', { 'class': 'secubox-column-left' }, [
|
||
this.renderSystemHealth(this.healthData),
|
||
this.renderAlerts(this.alertsData.alerts || [])
|
||
]),
|
||
// Right column
|
||
E('div', { 'class': 'secubox-column-right' }, [
|
||
this.renderActiveModules(),
|
||
this.renderQuickActions()
|
||
])
|
||
]);
|
||
|
||
container.appendChild(mainGrid);
|
||
|
||
// Start auto-refresh poll
|
||
poll.add(function() {
|
||
return self.refreshData().then(function() {
|
||
self.updateDynamicElements();
|
||
});
|
||
}, 30); // Refresh every 30 seconds
|
||
|
||
return container;
|
||
},
|
||
|
||
renderHeader: function(status) {
|
||
return E('div', { 'class': 'secubox-header' }, [
|
||
E('div', { 'class': 'secubox-header-content' }, [
|
||
E('div', {}, [
|
||
E('h2', {}, '🚀 SecuBox Control Center'),
|
||
E('p', { 'class': 'secubox-subtitle' }, 'Security & Network Management Suite')
|
||
]),
|
||
E('div', { 'class': 'secubox-header-info' }, [
|
||
E('span', { 'class': 'secubox-badge secubox-badge-version' },
|
||
'v' + (status.version || '0.1.0')),
|
||
E('span', { 'class': 'secubox-badge' },
|
||
'⏱️ ' + API.formatUptime(status.uptime)),
|
||
E('span', { 'class': 'secubox-badge', 'id': 'secubox-hostname' },
|
||
'🖥️ ' + (status.hostname || 'SecuBox'))
|
||
])
|
||
])
|
||
]);
|
||
},
|
||
|
||
renderStatsOverview: function() {
|
||
var modules = this.dashboardData.modules || [];
|
||
var counts = this.dashboardData.counts || {};
|
||
|
||
var stats = [
|
||
{
|
||
label: 'Total Modules',
|
||
value: counts.total || modules.length,
|
||
icon: '📦',
|
||
color: '#6366f1',
|
||
id: 'stat-total'
|
||
},
|
||
{
|
||
label: 'Installed',
|
||
value: counts.installed || 0,
|
||
icon: '✓',
|
||
color: '#22c55e',
|
||
id: 'stat-installed'
|
||
},
|
||
{
|
||
label: 'Running',
|
||
value: counts.running || 0,
|
||
icon: '▶',
|
||
color: '#00ab44',
|
||
id: 'stat-running'
|
||
},
|
||
{
|
||
label: 'Alerts',
|
||
value: (this.alertsData.alerts || []).length,
|
||
icon: '⚠️',
|
||
color: '#f59e0b',
|
||
id: 'stat-alerts'
|
||
}
|
||
];
|
||
|
||
return E('div', { 'class': 'secubox-stats-grid' },
|
||
stats.map(function(stat) {
|
||
return E('div', {
|
||
'class': 'secubox-stat-card',
|
||
'style': 'border-top: 3px solid ' + stat.color
|
||
}, [
|
||
E('div', { 'class': 'secubox-stat-icon' }, stat.icon),
|
||
E('div', { 'class': 'secubox-stat-content' }, [
|
||
E('div', {
|
||
'class': 'secubox-stat-value',
|
||
'id': stat.id,
|
||
'style': 'color: ' + stat.color
|
||
}, stat.value),
|
||
E('div', { 'class': 'secubox-stat-label' }, stat.label)
|
||
])
|
||
]);
|
||
})
|
||
);
|
||
},
|
||
|
||
renderSystemHealth: function(health) {
|
||
var cpu = health.cpu || {};
|
||
var memory = health.memory || {};
|
||
var disk = health.disk || {};
|
||
|
||
return E('div', { 'class': 'secubox-card' }, [
|
||
E('h3', { 'class': 'secubox-card-title' }, '📊 System Health'),
|
||
E('div', { 'class': 'secubox-health-grid' }, [
|
||
this.renderCircularGauge('CPU', cpu.percent || 0,
|
||
'Load: ' + (cpu.load_1min || '0.00'), 'cpu', '#6366f1'),
|
||
this.renderCircularGauge('Memory', memory.percent || 0,
|
||
API.formatBytes((memory.used_kb || 0) * 1024) + ' / ' +
|
||
API.formatBytes((memory.total_kb || 0) * 1024), 'memory', '#22c55e'),
|
||
this.renderCircularGauge('Disk', disk.percent || 0,
|
||
API.formatBytes((disk.used_kb || 0) * 1024) + ' / ' +
|
||
API.formatBytes((disk.total_kb || 0) * 1024), 'disk', '#f59e0b')
|
||
])
|
||
]);
|
||
},
|
||
|
||
renderCircularGauge: function(label, percent, details, id, baseColor) {
|
||
var color = percent < 70 ? '#22c55e' : percent < 85 ? '#f59e0b' : '#ef4444';
|
||
var radius = 45;
|
||
var circumference = 2 * Math.PI * radius;
|
||
var offset = circumference - (percent / 100) * circumference;
|
||
|
||
return E('div', { 'class': 'secubox-gauge-container' }, [
|
||
E('div', { 'class': 'secubox-gauge' }, [
|
||
E('svg', { 'viewBox': '0 0 120 120', 'class': 'secubox-gauge-svg' }, [
|
||
// Background circle
|
||
E('circle', {
|
||
'cx': '60',
|
||
'cy': '60',
|
||
'r': radius,
|
||
'fill': 'none',
|
||
'stroke': '#f1f5f9',
|
||
'stroke-width': '10'
|
||
}),
|
||
// Progress circle
|
||
E('circle', {
|
||
'id': 'gauge-' + id,
|
||
'cx': '60',
|
||
'cy': '60',
|
||
'r': radius,
|
||
'fill': 'none',
|
||
'stroke': color,
|
||
'stroke-width': '10',
|
||
'stroke-linecap': 'round',
|
||
'stroke-dasharray': circumference,
|
||
'stroke-dashoffset': offset,
|
||
'transform': 'rotate(-90 60 60)',
|
||
'class': 'secubox-gauge-progress'
|
||
})
|
||
]),
|
||
E('div', { 'class': 'secubox-gauge-content' }, [
|
||
E('div', {
|
||
'class': 'secubox-gauge-percent',
|
||
'id': 'gauge-' + id + '-percent',
|
||
'style': 'color: ' + color
|
||
}, Math.round(percent) + '%'),
|
||
E('div', { 'class': 'secubox-gauge-label' }, label)
|
||
])
|
||
]),
|
||
E('div', {
|
||
'class': 'secubox-gauge-details',
|
||
'id': 'gauge-' + id + '-details'
|
||
}, details)
|
||
]);
|
||
},
|
||
|
||
renderActiveModules: function() {
|
||
var self = this;
|
||
var modules = this.dashboardData.modules || [];
|
||
var activeModules = modules.filter(function(m) { return m.installed; });
|
||
|
||
// Apply category filter
|
||
var filteredModules = this.activeFilter === 'all' ? activeModules :
|
||
activeModules.filter(function(m) {
|
||
return m.category === self.activeFilter;
|
||
});
|
||
|
||
// Map module IDs to their dashboard paths
|
||
var modulePaths = {
|
||
'crowdsec': 'admin/secubox/crowdsec/overview',
|
||
'netdata': 'admin/secubox/netdata/dashboard',
|
||
'netifyd': 'admin/secubox/netifyd/overview',
|
||
'wireguard': 'admin/secubox/wireguard/overview',
|
||
'network_modes': 'admin/secubox/network-modes/overview',
|
||
'client_guardian': 'admin/secubox/client-guardian/overview',
|
||
'system_hub': 'admin/secubox/system-hub/overview',
|
||
'bandwidth_manager': 'admin/secubox/bandwidth-manager/overview',
|
||
'auth_guardian': 'admin/secubox/auth-guardian/overview',
|
||
'media_flow': 'admin/secubox/mediaflow/dashboard',
|
||
'vhost_manager': 'admin/secubox/vhosts/overview',
|
||
'traffic_shaper': 'admin/secubox/traffic-shaper/overview',
|
||
'cdn_cache': 'admin/secubox/cdn-cache/overview',
|
||
'ksm_manager': 'admin/secubox/ksm-manager/overview'
|
||
};
|
||
|
||
var moduleCards = filteredModules.map(function(module) {
|
||
var status = module.status || 'unknown';
|
||
var statusClass = status;
|
||
var dashboardPath = modulePaths[module.id] || ('admin/secubox/' + module.id);
|
||
|
||
// Status label mapping (v0.3.1)
|
||
var statusLabels = {
|
||
'active': '✓ Activé',
|
||
'disabled': '○ Désactivé',
|
||
'error': '⚠️ Erreur',
|
||
'unknown': '? Inconnu'
|
||
};
|
||
|
||
var statusLabel = statusLabels[status] || '○ Désactivé';
|
||
|
||
return E('a', {
|
||
'href': L.url(dashboardPath),
|
||
'class': 'secubox-module-link secubox-module-' + statusClass
|
||
}, [
|
||
E('div', {
|
||
'class': 'secubox-module-mini-card',
|
||
'style': 'border-left: 4px solid ' + (module.color || '#64748b')
|
||
}, [
|
||
E('div', { 'class': 'secubox-module-mini-header' }, [
|
||
E('span', { 'class': 'secubox-module-mini-icon' }, module.icon || '📦'),
|
||
E('span', {
|
||
'class': 'secubox-status-dot secubox-status-' + statusClass,
|
||
'title': statusLabel
|
||
})
|
||
]),
|
||
E('div', { 'class': 'secubox-module-mini-body' }, [
|
||
E('div', { 'class': 'secubox-module-mini-name' }, module.name || module.id),
|
||
E('div', { 'class': 'secubox-module-mini-status' }, statusLabel)
|
||
])
|
||
])
|
||
]);
|
||
});
|
||
|
||
// Filter tabs
|
||
var filters = [
|
||
{ id: 'all', label: 'All', icon: '📦' },
|
||
{ id: 'security', label: 'Security', icon: '🛡️' },
|
||
{ id: 'network', label: 'Network', icon: '🌐' },
|
||
{ id: 'system', label: 'System', icon: '⚙️' },
|
||
{ id: 'monitoring', label: 'Monitoring', icon: '📊' }
|
||
];
|
||
|
||
var filterTabButtons = filters.map(function(filter) {
|
||
var isActive = self.activeFilter === filter.id;
|
||
return E('div', {
|
||
'class': 'secubox-filter-tab' + (isActive ? ' active' : ''),
|
||
'click': function() {
|
||
self.activeFilter = filter.id;
|
||
self.updateModulesGrid();
|
||
}
|
||
}, [
|
||
E('span', { 'class': 'secubox-tab-icon' }, filter.icon),
|
||
E('span', { 'class': 'secubox-tab-label' }, filter.label)
|
||
]);
|
||
});
|
||
|
||
filterTabButtons.push(
|
||
E('div', {
|
||
'class': 'secubox-filter-tab secubox-tab-bonus',
|
||
'click': function() {
|
||
window.location.href = L.url('admin/secubox/help');
|
||
}
|
||
}, [
|
||
E('span', { 'class': 'secubox-tab-icon' }, '✨'),
|
||
E('span', { 'class': 'secubox-tab-label' }, _('Bonus · Help à SecuBox'))
|
||
])
|
||
);
|
||
|
||
var filterTabs = E('div', { 'class': 'secubox-filter-tabs' }, filterTabButtons);
|
||
|
||
return E('div', { 'class': 'secubox-card' }, [
|
||
E('h3', { 'class': 'secubox-card-title' }, '🎯 Active Modules (' + activeModules.length + ')'),
|
||
filterTabs,
|
||
E('div', {
|
||
'class': 'secubox-modules-mini-grid',
|
||
'id': 'modules-grid'
|
||
},
|
||
moduleCards.length > 0 ? moduleCards : [
|
||
E('p', { 'class': 'secubox-empty-state' },
|
||
this.activeFilter === 'all' ? 'No modules installed' :
|
||
'No ' + this.activeFilter + ' modules installed')
|
||
]
|
||
)
|
||
]);
|
||
},
|
||
|
||
updateModulesGrid: function() {
|
||
var container = document.getElementById('modules-grid');
|
||
if (!container) return;
|
||
|
||
var modules = this.dashboardData.modules || [];
|
||
var activeModules = modules.filter(function(m) { return m.installed; });
|
||
var self = this;
|
||
|
||
// Filter definitions (same as in renderActiveModules)
|
||
var filters = [
|
||
{ id: 'all', label: 'All', icon: '📦' },
|
||
{ id: 'security', label: 'Security', icon: '🛡️' },
|
||
{ id: 'network', label: 'Network', icon: '🌐' },
|
||
{ id: 'system', label: 'System', icon: '⚙️' },
|
||
{ id: 'monitoring', label: 'Monitoring', icon: '📊' }
|
||
];
|
||
|
||
// Apply category filter
|
||
var filteredModules = this.activeFilter === 'all' ? activeModules :
|
||
activeModules.filter(function(m) {
|
||
return m.category === self.activeFilter;
|
||
});
|
||
|
||
// Map module IDs to their dashboard paths
|
||
var modulePaths = {
|
||
'crowdsec': 'admin/secubox/crowdsec/overview',
|
||
'netdata': 'admin/secubox/netdata/dashboard',
|
||
'netifyd': 'admin/secubox/netifyd/overview',
|
||
'wireguard': 'admin/secubox/wireguard/overview',
|
||
'network_modes': 'admin/secubox/network-modes/overview',
|
||
'client_guardian': 'admin/secubox/client-guardian/overview',
|
||
'system_hub': 'admin/secubox/system-hub/overview',
|
||
'bandwidth_manager': 'admin/secubox/bandwidth-manager/overview',
|
||
'auth_guardian': 'admin/secubox/auth-guardian/overview',
|
||
'media_flow': 'admin/secubox/mediaflow/dashboard',
|
||
'vhost_manager': 'admin/secubox/vhosts/overview',
|
||
'traffic_shaper': 'admin/secubox/traffic-shaper/overview',
|
||
'cdn_cache': 'admin/secubox/cdn-cache/overview',
|
||
'ksm_manager': 'admin/secubox/ksm-manager/overview'
|
||
};
|
||
|
||
var moduleCards = filteredModules.map(function(module) {
|
||
var status = module.status || 'unknown';
|
||
var statusClass = status;
|
||
var dashboardPath = modulePaths[module.id] || ('admin/secubox/' + module.id);
|
||
|
||
// Status label mapping (v0.3.1)
|
||
var statusLabels = {
|
||
'active': '✓ Activé',
|
||
'disabled': '○ Désactivé',
|
||
'error': '⚠️ Erreur',
|
||
'unknown': '? Inconnu'
|
||
};
|
||
|
||
var statusLabel = statusLabels[status] || '○ Désactivé';
|
||
|
||
return E('a', {
|
||
'href': L.url(dashboardPath),
|
||
'class': 'secubox-module-link secubox-module-' + statusClass
|
||
}, [
|
||
E('div', {
|
||
'class': 'secubox-module-mini-card',
|
||
'style': 'border-left: 4px solid ' + (module.color || '#64748b')
|
||
}, [
|
||
E('div', { 'class': 'secubox-module-mini-header' }, [
|
||
E('span', { 'class': 'secubox-module-mini-icon' }, module.icon || '📦'),
|
||
E('span', {
|
||
'class': 'secubox-status-dot secubox-status-' + statusClass,
|
||
'title': statusLabel
|
||
})
|
||
]),
|
||
E('div', { 'class': 'secubox-module-mini-body' }, [
|
||
E('div', { 'class': 'secubox-module-mini-name' }, module.name || module.id),
|
||
E('div', { 'class': 'secubox-module-mini-status' }, statusLabel)
|
||
])
|
||
])
|
||
]);
|
||
});
|
||
|
||
// Update filter tab active states
|
||
var tabs = container.parentElement.querySelectorAll('.secubox-filter-tab');
|
||
tabs.forEach(function(tab) {
|
||
var filterMatch = false;
|
||
filters.forEach(function(filter) {
|
||
if (tab.textContent.includes(filter.label) && self.activeFilter === filter.id) {
|
||
filterMatch = true;
|
||
}
|
||
});
|
||
if (filterMatch) {
|
||
tab.classList.add('active');
|
||
} else {
|
||
tab.classList.remove('active');
|
||
}
|
||
});
|
||
|
||
// Update content
|
||
dom.content(container, moduleCards.length > 0 ? moduleCards : [
|
||
E('p', { 'class': 'secubox-empty-state' },
|
||
this.activeFilter === 'all' ? 'No modules installed' :
|
||
'No ' + this.activeFilter + ' modules installed')
|
||
]);
|
||
},
|
||
|
||
renderQuickActions: function() {
|
||
var self = this;
|
||
var actions = [
|
||
{ name: 'restart_rpcd', label: 'RPCD', icon: '🔄', color: '#6366f1' },
|
||
{ name: 'restart_uhttpd', label: 'Web Server', icon: '🌐', color: '#00ab44' },
|
||
{ name: 'restart_network', label: 'Network', icon: '📡', color: '#06b6d4' },
|
||
{ name: 'restart_firewall', label: 'Firewall', icon: '🛡️', color: '#ef4444' },
|
||
{ name: 'clear_cache', label: 'Clear Cache', icon: '🧹', color: '#f59e0b' },
|
||
{ name: 'backup_config', label: 'Backup', icon: '💾', color: '#8b5cf6' }
|
||
];
|
||
|
||
var buttons = actions.map(function(action) {
|
||
return E('button', {
|
||
'class': 'secubox-action-btn',
|
||
'style': 'border-color: ' + action.color,
|
||
'click': function() {
|
||
self.executeQuickAction(action.name, action.label);
|
||
}
|
||
}, [
|
||
E('span', { 'class': 'secubox-action-icon' }, action.icon),
|
||
E('span', { 'class': 'secubox-action-label' }, action.label)
|
||
]);
|
||
});
|
||
|
||
// Add Fix Permissions button (v0.3.1)
|
||
buttons.push(
|
||
E('button', {
|
||
'class': 'secubox-action-btn',
|
||
'style': 'border-color: #f97316',
|
||
'click': function() {
|
||
self.executeFixPermissions();
|
||
}
|
||
}, [
|
||
E('span', { 'class': 'secubox-action-icon' }, '🔧'),
|
||
E('span', { 'class': 'secubox-action-label' }, 'Fix Perms')
|
||
])
|
||
);
|
||
|
||
return E('div', { 'class': 'secubox-card' }, [
|
||
E('h3', { 'class': 'secubox-card-title' }, '⚡ Quick Actions'),
|
||
E('div', { 'class': 'secubox-actions-grid' }, buttons)
|
||
]);
|
||
},
|
||
|
||
executeQuickAction: function(action, label) {
|
||
var self = this;
|
||
ui.showModal(_('Quick Action'), [
|
||
E('p', { 'class': 'spinning' }, _('Executing: ') + label + '...')
|
||
]);
|
||
|
||
API.quickAction(action).then(function(result) {
|
||
ui.hideModal();
|
||
if (result && result.success) {
|
||
ui.addNotification(null, E('p', '✓ ' + (result.message || 'Action completed')), 'info');
|
||
// Refresh data after action
|
||
setTimeout(function() {
|
||
self.refreshData().then(function() {
|
||
self.updateDynamicElements();
|
||
});
|
||
}, 2000);
|
||
} else {
|
||
ui.addNotification(null, E('p', '✗ ' + (result.message || 'Action failed')), 'error');
|
||
}
|
||
}).catch(function(err) {
|
||
ui.hideModal();
|
||
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
|
||
});
|
||
},
|
||
|
||
executeFixPermissions: function() {
|
||
var self = this;
|
||
ui.showModal(_('Fixing Permissions'), [
|
||
E('p', { 'class': 'spinning' }, _('Fixing file permissions and restarting services...'))
|
||
]);
|
||
|
||
API.fixPermissions().then(function(result) {
|
||
ui.hideModal();
|
||
if (result && result.success) {
|
||
ui.addNotification(null, E('p', '✓ Permissions fixed successfully'), 'info');
|
||
// Show output in console
|
||
if (result.output) {
|
||
console.log('Fix Permissions Output:\n' + result.output);
|
||
}
|
||
// Refresh data after fixing permissions
|
||
setTimeout(function() {
|
||
self.refreshData().then(function() {
|
||
self.updateDynamicElements();
|
||
});
|
||
}, 2000);
|
||
} else {
|
||
ui.addNotification(null, E('p', '✗ ' + (result.message || 'Failed to fix permissions')), 'error');
|
||
if (result.output) {
|
||
console.error('Fix Permissions Error:\n' + result.output);
|
||
}
|
||
}
|
||
}).catch(function(err) {
|
||
ui.hideModal();
|
||
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
|
||
});
|
||
},
|
||
|
||
renderAlerts: function(alerts) {
|
||
if (!alerts || alerts.length === 0) {
|
||
return E('div', { 'class': 'secubox-card' }, [
|
||
E('h3', { 'class': 'secubox-card-title' }, '✓ System Status'),
|
||
E('div', { 'class': 'secubox-alert secubox-alert-success' }, [
|
||
E('span', {}, '✓ All systems operational')
|
||
])
|
||
]);
|
||
}
|
||
|
||
var alertItems = alerts.slice(0, 5).map(function(alert) {
|
||
var severityClass = 'secubox-alert-' + (alert.severity || 'info');
|
||
var severityIcon = alert.severity === 'error' ? '✗' :
|
||
alert.severity === 'warning' ? '⚠️' : 'ℹ️';
|
||
return E('div', { 'class': 'secubox-alert ' + severityClass }, [
|
||
E('span', { 'class': 'secubox-alert-icon' }, severityIcon),
|
||
E('div', { 'class': 'secubox-alert-content' }, [
|
||
E('strong', {}, alert.module || 'System'),
|
||
E('span', {}, ': ' + alert.message)
|
||
])
|
||
]);
|
||
});
|
||
|
||
return E('div', { 'class': 'secubox-card' }, [
|
||
E('h3', { 'class': 'secubox-card-title' },
|
||
'⚠️ Alerts (' + alerts.length + ')'),
|
||
E('div', { 'class': 'secubox-alerts-list' }, alertItems),
|
||
alerts.length > 5 ? E('a', {
|
||
'href': '#',
|
||
'class': 'secubox-view-all',
|
||
'click': function(ev) {
|
||
ev.preventDefault();
|
||
window.location.hash = '#admin/secubox/alerts';
|
||
}
|
||
}, 'View all alerts →') : null
|
||
]);
|
||
},
|
||
|
||
updateDynamicElements: function() {
|
||
// Update stats
|
||
var counts = this.dashboardData.counts || {};
|
||
var totalEl = document.getElementById('stat-total');
|
||
var installedEl = document.getElementById('stat-installed');
|
||
var runningEl = document.getElementById('stat-running');
|
||
var alertsEl = document.getElementById('stat-alerts');
|
||
|
||
if (totalEl) totalEl.textContent = counts.total || 0;
|
||
if (installedEl) installedEl.textContent = counts.installed || 0;
|
||
if (runningEl) runningEl.textContent = counts.running || 0;
|
||
if (alertsEl) alertsEl.textContent = (this.alertsData.alerts || []).length;
|
||
|
||
// Update health bars
|
||
var health = this.healthData;
|
||
this.updateHealthBar('cpu', health.cpu);
|
||
this.updateHealthBar('memory', health.memory);
|
||
this.updateHealthBar('disk', health.disk);
|
||
},
|
||
|
||
updateHealthBar: function(type, data) {
|
||
if (!data) return;
|
||
|
||
var percent = data.percent || 0;
|
||
var color = percent < 70 ? '#22c55e' : percent < 85 ? '#f59e0b' : '#ef4444';
|
||
|
||
var percentEl = document.getElementById('gauge-' + type + '-percent');
|
||
var gaugeEl = document.getElementById('gauge-' + type);
|
||
|
||
if (percentEl) {
|
||
percentEl.textContent = Math.round(percent) + '%';
|
||
percentEl.style.color = color;
|
||
}
|
||
if (gaugeEl) {
|
||
var radius = 45;
|
||
var circumference = 2 * Math.PI * radius;
|
||
var offset = circumference - (percent / 100) * circumference;
|
||
gaugeEl.setAttribute('stroke-dashoffset', offset);
|
||
gaugeEl.setAttribute('stroke', color);
|
||
}
|
||
|
||
// Update details
|
||
var detailsEl = document.getElementById('gauge-' + type + '-details');
|
||
if (detailsEl && type === 'cpu') {
|
||
detailsEl.textContent = 'Load: ' + (data.load_1min || '0.00');
|
||
} else if (detailsEl && type === 'memory') {
|
||
detailsEl.textContent = API.formatBytes((data.used_kb || 0) * 1024) + ' / ' +
|
||
API.formatBytes((data.total_kb || 0) * 1024);
|
||
} else if (detailsEl && type === 'disk') {
|
||
detailsEl.textContent = API.formatBytes((data.used_kb || 0) * 1024) + ' / ' +
|
||
API.formatBytes((data.total_kb || 0) * 1024);
|
||
}
|
||
},
|
||
|
||
handleSaveApply: null,
|
||
handleSave: null,
|
||
handleReset: null
|
||
});
|