feat(secubox): modernize dashboard with dynamic updates and module links
Major dashboard improvements: ✨ New Features: - Auto-refresh every 30 seconds using poll API - Dynamic updates without page reload - Direct links to module dashboards (clickable cards) - Modern card-based layout with responsive grid - Stats overview with 4 key metrics - Progress bars instead of circular gauges - Better visual hierarchy and spacing 🎨 Design Improvements: - Gradient header with version badge - Animated stat cards with hover effects - Color-coded health indicators - Pulsing status dots for running modules - Modern CSS with shadows and transitions - Responsive layout for mobile/tablet - Smooth animations and transitions 🔗 Module Navigation: - Clickable module cards with direct links - Smart path mapping for all 14 modules - Visual running/stopped indicators - Hover effects for better UX 📊 Better Data Display: - Horizontal progress bars for CPU/Memory/Disk - Real-time value updates - Compact module grid with icons - Alert system with severity colors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
45a49f32b7
commit
b3a02ffe14
@ -0,0 +1,407 @@
|
||||
/* SecuBox Dashboard Styles */
|
||||
|
||||
.secubox-dashboard {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.secubox-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.secubox-header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.secubox-header h2 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.secubox-subtitle {
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.secubox-header-info {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.secubox-badge {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.secubox-badge-version {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Stats Grid */
|
||||
.secubox-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.secubox-stat-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.secubox-stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.secubox-stat-icon {
|
||||
font-size: 32px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.secubox-stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.secubox-stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.secubox-stat-label {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Main Grid Layout */
|
||||
.secubox-main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.secubox-main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.secubox-column-left,
|
||||
.secubox-column-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.secubox-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.secubox-card-title {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
border-bottom: 2px solid #f1f5f9;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
/* Progress Bars */
|
||||
.secubox-health-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.secubox-progress-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.secubox-progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.secubox-progress-label {
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.secubox-progress-value {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.secubox-progress-bar {
|
||||
height: 8px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.secubox-progress-fill {
|
||||
height: 100%;
|
||||
transition: width 0.6s ease, background 0.3s ease;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.secubox-progress-details {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* Active Modules */
|
||||
.secubox-modules-mini-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.secubox-module-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: transform 0.2s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.secubox-module-link:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.secubox-module-mini-card {
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.secubox-module-link:hover .secubox-module-mini-card {
|
||||
background: #f1f5f9;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.secubox-module-mini-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.secubox-module-mini-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.secubox-status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.secubox-status-dot.secubox-status-running {
|
||||
background: #22c55e;
|
||||
box-shadow: 0 0 8px rgba(34, 197, 94, 0.5);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.secubox-status-dot.secubox-status-stopped {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.secubox-module-mini-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.secubox-module-mini-name {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.secubox-module-mini-status {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.secubox-module-running .secubox-module-mini-status {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
/* Quick Actions */
|
||||
.secubox-actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.secubox-action-btn {
|
||||
background: white;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.secubox-action-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
.secubox-action-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.secubox-action-label {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.secubox-alerts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.secubox-alert {
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.secubox-alert-success {
|
||||
background: #f0fdf4;
|
||||
border-left: 4px solid #22c55e;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.secubox-alert-info {
|
||||
background: #eff6ff;
|
||||
border-left: 4px solid #3b82f6;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.secubox-alert-warning {
|
||||
background: #fffbeb;
|
||||
border-left: 4px solid #f59e0b;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.secubox-alert-error {
|
||||
background: #fef2f2;
|
||||
border-left: 4px solid #ef4444;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.secubox-alert-icon {
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.secubox-alert-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.secubox-view-all {
|
||||
display: inline-block;
|
||||
margin-top: 12px;
|
||||
color: #6366f1;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.secubox-view-all:hover {
|
||||
color: #4f46e5;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.secubox-empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.secubox-stat-card,
|
||||
.secubox-card {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.secubox-stat-card:nth-child(1) { animation-delay: 0.1s; }
|
||||
.secubox-stat-card:nth-child(2) { animation-delay: 0.2s; }
|
||||
.secubox-stat-card:nth-child(3) { animation-delay: 0.3s; }
|
||||
.secubox-stat-card:nth-child(4) { animation-delay: 0.4s; }
|
||||
@ -3,137 +3,287 @@
|
||||
'require ui';
|
||||
'require secubox/api as API';
|
||||
'require dom';
|
||||
'require poll';
|
||||
|
||||
// Load CSS
|
||||
document.head.appendChild(E('link', {
|
||||
'rel': 'stylesheet',
|
||||
'type': 'text/css',
|
||||
'href': L.resource('secubox/secubox.css')
|
||||
'href': L.resource('secubox/dashboard.css')
|
||||
}));
|
||||
|
||||
return view.extend({
|
||||
dashboardData: null,
|
||||
healthData: null,
|
||||
alertsData: null,
|
||||
|
||||
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 dashboard = data[0] || {};
|
||||
var health = data[1] || {};
|
||||
var alertsData = data[2] || {};
|
||||
var self = this;
|
||||
var container = E('div', { 'class': 'secubox-dashboard' });
|
||||
|
||||
var status = dashboard.status || {};
|
||||
var modules = dashboard.modules || [];
|
||||
var counts = dashboard.counts || {};
|
||||
var alerts = alertsData.alerts || [];
|
||||
// Header with version
|
||||
var status = this.dashboardData.status || {};
|
||||
container.appendChild(this.renderHeader(status));
|
||||
|
||||
var container = E('div', { 'class': 'cbi-map' });
|
||||
// Stats Overview Cards
|
||||
container.appendChild(this.renderStatsOverview());
|
||||
|
||||
// Header
|
||||
container.appendChild(E('h2', {}, 'SecuBox Central Hub'));
|
||||
container.appendChild(E('p', {},
|
||||
'Hostname: ' + (status.hostname || 'Unknown') + ' | ' +
|
||||
'Uptime: ' + API.formatUptime(status.uptime) + ' | ' +
|
||||
'Load: ' + (status.load || '0.00')
|
||||
));
|
||||
// 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()
|
||||
])
|
||||
]);
|
||||
|
||||
// System Health Section
|
||||
container.appendChild(this.renderSystemHealth(health));
|
||||
container.appendChild(mainGrid);
|
||||
|
||||
// Quick Actions Section
|
||||
container.appendChild(this.renderQuickActions());
|
||||
|
||||
// Alerts Section
|
||||
if (alerts.length > 0) {
|
||||
container.appendChild(this.renderAlerts(alerts));
|
||||
}
|
||||
|
||||
// Modules Grid
|
||||
container.appendChild(this.renderModulesGrid(modules));
|
||||
// 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 Central Hub'),
|
||||
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.0.1-beta')),
|
||||
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 || {};
|
||||
var network = health.network || {};
|
||||
|
||||
var section = E('div', { 'class': 'secubox-health-section' }, [
|
||||
E('h3', {}, 'System Health'),
|
||||
return E('div', { 'class': 'secubox-card' }, [
|
||||
E('h3', { 'class': 'secubox-card-title' }, '📊 System Health'),
|
||||
E('div', { 'class': 'secubox-health-grid' }, [
|
||||
this.renderGauge('CPU', cpu.percent || 0, '%', cpu.load_1min),
|
||||
this.renderGauge('Memory', memory.percent || 0, '%',
|
||||
API.formatBytes(memory.used_kb * 1024) + ' / ' + API.formatBytes(memory.total_kb * 1024)),
|
||||
this.renderGauge('Disk', disk.percent || 0, '%',
|
||||
API.formatBytes(disk.used_kb * 1024) + ' / ' + API.formatBytes(disk.total_kb * 1024)),
|
||||
this.renderGauge('Network', 0, '',
|
||||
'RX: ' + API.formatBytes(network.rx_bytes) + ' | TX: ' + API.formatBytes(network.tx_bytes))
|
||||
this.renderProgressBar('CPU', cpu.percent || 0, cpu.load_1min || '0.00', 'cpu'),
|
||||
this.renderProgressBar('Memory', memory.percent || 0,
|
||||
API.formatBytes((memory.used_kb || 0) * 1024) + ' / ' +
|
||||
API.formatBytes((memory.total_kb || 0) * 1024), 'memory'),
|
||||
this.renderProgressBar('Disk', disk.percent || 0,
|
||||
API.formatBytes((disk.used_kb || 0) * 1024) + ' / ' +
|
||||
API.formatBytes((disk.total_kb || 0) * 1024), 'disk')
|
||||
])
|
||||
]);
|
||||
|
||||
return section;
|
||||
},
|
||||
|
||||
renderGauge: function(label, percent, unit, details) {
|
||||
renderProgressBar: function(label, percent, details, id) {
|
||||
var color = percent < 70 ? '#22c55e' : percent < 85 ? '#f59e0b' : '#ef4444';
|
||||
|
||||
return E('div', { 'class': 'secubox-gauge' }, [
|
||||
E('div', { 'class': 'secubox-gauge-label' }, label),
|
||||
E('div', { 'class': 'secubox-gauge-chart' }, [
|
||||
E('svg', { 'width': '120', 'height': '120', 'viewBox': '0 0 120 120' }, [
|
||||
E('circle', {
|
||||
'cx': '60', 'cy': '60', 'r': '54',
|
||||
'fill': 'none', 'stroke': '#1e293b', 'stroke-width': '12'
|
||||
}),
|
||||
E('circle', {
|
||||
'cx': '60', 'cy': '60', 'r': '54',
|
||||
'fill': 'none', 'stroke': color, 'stroke-width': '12',
|
||||
'stroke-dasharray': (339.292 * percent / 100) + ' 339.292',
|
||||
'stroke-linecap': 'round',
|
||||
'transform': 'rotate(-90 60 60)'
|
||||
}),
|
||||
E('text', {
|
||||
'x': '60', 'y': '65', 'text-anchor': 'middle',
|
||||
'font-size': '24', 'font-weight': 'bold', 'fill': color
|
||||
}, percent + unit)
|
||||
])
|
||||
return E('div', { 'class': 'secubox-progress-item' }, [
|
||||
E('div', { 'class': 'secubox-progress-header' }, [
|
||||
E('span', { 'class': 'secubox-progress-label' }, label),
|
||||
E('span', {
|
||||
'class': 'secubox-progress-value',
|
||||
'id': 'health-' + id + '-percent',
|
||||
'style': 'color: ' + color
|
||||
}, percent + '%')
|
||||
]),
|
||||
E('div', { 'class': 'secubox-gauge-details' }, details || '')
|
||||
E('div', { 'class': 'secubox-progress-bar' }, [
|
||||
E('div', {
|
||||
'class': 'secubox-progress-fill',
|
||||
'id': 'health-' + id + '-bar',
|
||||
'style': 'width: ' + percent + '%; background: ' + color
|
||||
})
|
||||
]),
|
||||
E('div', {
|
||||
'class': 'secubox-progress-details',
|
||||
'id': 'health-' + id + '-details'
|
||||
}, details)
|
||||
]);
|
||||
},
|
||||
|
||||
renderActiveModules: function() {
|
||||
var modules = this.dashboardData.modules || [];
|
||||
var activeModules = modules.filter(function(m) { return m.installed; });
|
||||
|
||||
// Map module IDs to their dashboard paths
|
||||
var modulePaths = {
|
||||
'crowdsec': 'admin/secubox/security/crowdsec',
|
||||
'netdata': 'admin/secubox/monitoring/netdata',
|
||||
'netifyd': 'admin/secubox/security/netifyd',
|
||||
'wireguard': 'admin/secubox/network/wireguard',
|
||||
'network_modes': 'admin/secubox/network/modes',
|
||||
'client_guardian': 'admin/secubox/security/guardian',
|
||||
'system_hub': 'admin/secubox/system/hub',
|
||||
'bandwidth_manager': 'admin/secubox/network/bandwidth',
|
||||
'auth_guardian': 'admin/secubox/security/auth',
|
||||
'media_flow': 'admin/secubox/network/media',
|
||||
'vhost_manager': 'admin/secubox/system/vhost',
|
||||
'traffic_shaper': 'admin/secubox/network/shaper',
|
||||
'cdn_cache': 'admin/secubox/network/cdn',
|
||||
'ksm_manager': 'admin/secubox/security/ksm'
|
||||
};
|
||||
|
||||
var moduleCards = activeModules.map(function(module) {
|
||||
var isRunning = module.running;
|
||||
var statusClass = isRunning ? 'running' : 'stopped';
|
||||
var dashboardPath = modulePaths[module.id] || ('admin/secubox/' + module.id);
|
||||
|
||||
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': isRunning ? 'Running' : 'Stopped'
|
||||
})
|
||||
]),
|
||||
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' },
|
||||
isRunning ? '● Running' : '○ Stopped')
|
||||
])
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'secubox-card' }, [
|
||||
E('h3', { 'class': 'secubox-card-title' }, '🎯 Active Modules (' + activeModules.length + ')'),
|
||||
E('div', { 'class': 'secubox-modules-mini-grid' },
|
||||
moduleCards.length > 0 ? moduleCards : [
|
||||
E('p', { 'class': 'secubox-empty-state' }, 'No modules installed')
|
||||
]
|
||||
)
|
||||
]);
|
||||
},
|
||||
|
||||
renderQuickActions: function() {
|
||||
var self = this;
|
||||
var actions = [
|
||||
{ name: 'restart_rpcd', label: 'Restart RPCD', icon: '🔄' },
|
||||
{ name: 'restart_uhttpd', label: 'Restart uHTTPd', icon: '🌐' },
|
||||
{ name: 'clear_cache', label: 'Clear Cache', icon: '🧹' },
|
||||
{ name: 'backup_config', label: 'Backup Config', icon: '💾' },
|
||||
{ name: 'restart_network', label: 'Restart Network', icon: '📡' },
|
||||
{ name: 'restart_firewall', label: 'Restart Firewall', icon: '🛡️' }
|
||||
{ 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': 'cbi-button cbi-button-action',
|
||||
'class': 'secubox-action-btn',
|
||||
'style': 'border-color: ' + action.color,
|
||||
'click': function() {
|
||||
self.executeQuickAction(action.name, action.label);
|
||||
}
|
||||
}, action.icon + ' ' + action.label);
|
||||
}, [
|
||||
E('span', { 'class': 'secubox-action-icon' }, action.icon),
|
||||
E('span', { 'class': 'secubox-action-label' }, action.label)
|
||||
]);
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'secubox-quick-actions' }, [
|
||||
E('h3', {}, 'Quick Actions'),
|
||||
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 + '...')
|
||||
]);
|
||||
@ -141,9 +291,15 @@ return view.extend({
|
||||
API.quickAction(action).then(function(result) {
|
||||
ui.hideModal();
|
||||
if (result && result.success) {
|
||||
ui.addNotification(null, E('p', result.message || 'Action completed'), 'info');
|
||||
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');
|
||||
ui.addNotification(null, E('p', '✗ ' + (result.message || 'Action failed')), 'error');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
ui.hideModal();
|
||||
@ -152,64 +308,82 @@ return view.extend({
|
||||
},
|
||||
|
||||
renderAlerts: function(alerts) {
|
||||
var alertItems = alerts.map(function(alert) {
|
||||
var severityClass = 'secubox-alert-' + (alert.severity || 'info');
|
||||
return E('div', { 'class': 'secubox-alert ' + severityClass }, [
|
||||
E('strong', {}, alert.module + ': '),
|
||||
E('span', {}, alert.message)
|
||||
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')
|
||||
])
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
return E('div', { 'class': 'secubox-alerts-section' }, [
|
||||
E('h3', {}, 'Recent Alerts (' + alerts.length + ')'),
|
||||
E('div', { 'class': 'secubox-alerts-list' }, alertItems)
|
||||
]);
|
||||
},
|
||||
|
||||
renderModulesGrid: function(modules) {
|
||||
var moduleCards = modules.map(function(module) {
|
||||
var statusClass = module.installed ? (module.running ? 'running' : 'stopped') : 'not-installed';
|
||||
var statusIcon = module.installed ? (module.running ? '✓' : '✗') : '○';
|
||||
var statusColor = module.installed ? (module.running ? '#22c55e' : '#ef4444') : '#64748b';
|
||||
|
||||
return E('div', {
|
||||
'class': 'secubox-module-card',
|
||||
'data-status': statusClass
|
||||
}, [
|
||||
E('div', { 'class': 'secubox-module-header' }, [
|
||||
E('div', {
|
||||
'class': 'secubox-module-icon',
|
||||
'style': 'background-color: ' + (module.color || '#64748b')
|
||||
}, module.icon || '📦'),
|
||||
E('div', {
|
||||
'class': 'secubox-module-status',
|
||||
'style': 'color: ' + statusColor
|
||||
}, statusIcon)
|
||||
]),
|
||||
E('div', { 'class': 'secubox-module-body' }, [
|
||||
E('div', { 'class': 'secubox-module-name' }, module.name || module.id),
|
||||
E('div', { 'class': 'secubox-module-description' }, module.description || ''),
|
||||
E('div', { 'class': 'secubox-module-category' }, module.category || 'other')
|
||||
]),
|
||||
E('div', { 'class': 'secubox-module-footer' }, [
|
||||
module.installed ? E('a', {
|
||||
'href': '#',
|
||||
'class': 'cbi-button cbi-button-link',
|
||||
'click': function(ev) {
|
||||
ev.preventDefault();
|
||||
window.location.hash = '#admin/secubox/' + module.id;
|
||||
}
|
||||
}, 'Open Dashboard') : E('span', { 'class': 'secubox-not-installed' }, 'Not Installed')
|
||||
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-modules-section' }, [
|
||||
E('h3', {}, 'Installed Modules'),
|
||||
E('div', { 'class': 'secubox-modules-grid' }, moduleCards)
|
||||
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('health-' + type + '-percent');
|
||||
var barEl = document.getElementById('health-' + type + '-bar');
|
||||
|
||||
if (percentEl) {
|
||||
percentEl.textContent = percent + '%';
|
||||
percentEl.style.color = color;
|
||||
}
|
||||
if (barEl) {
|
||||
barEl.style.width = percent + '%';
|
||||
barEl.style.background = color;
|
||||
}
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
|
||||
Loading…
Reference in New Issue
Block a user