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>
893 lines
33 KiB
JavaScript
893 lines
33 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require ui';
|
|
'require dom';
|
|
'require poll';
|
|
'require system-hub/api as API';
|
|
'require secubox-theme/theme as Theme';
|
|
|
|
// Initialize global theme
|
|
Theme.init({ theme: 'dark', language: 'en' });
|
|
|
|
return view.extend({
|
|
healthData: null,
|
|
sysInfo: null,
|
|
|
|
load: function() {
|
|
return Promise.all([
|
|
API.getSystemInfo(),
|
|
API.getHealth()
|
|
]);
|
|
},
|
|
|
|
render: function(data) {
|
|
var self = this;
|
|
this.sysInfo = data[0] || {};
|
|
this.healthData = data[1] || {};
|
|
|
|
var container = E('div', { 'class': 'system-hub-dashboard' }, [
|
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }),
|
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }),
|
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/overview.css') }),
|
|
|
|
// Header
|
|
this.renderHeader(),
|
|
|
|
// Stats Overview (like SecuBox)
|
|
this.renderStatsOverview(),
|
|
|
|
// System Info Grid (4 columns per prompt)
|
|
this.renderSystemInfoGrid(),
|
|
|
|
// Real-Time Performance Metrics (v0.3.2 - NEW: modern histograms)
|
|
E('h3', { 'class': 'sh-section-title' }, '📈 Real-Time Performance Metrics'),
|
|
this.renderRealtimeMetrics(),
|
|
|
|
// Quick Status Indicators (per prompt)
|
|
E('h3', { 'class': 'sh-section-title' }, '⚡ System Status'),
|
|
this.renderQuickStatusIndicators()
|
|
]);
|
|
|
|
// Setup auto-refresh
|
|
poll.add(L.bind(function() {
|
|
return Promise.all([
|
|
API.getSystemInfo(),
|
|
API.getHealth()
|
|
]).then(L.bind(function(refreshData) {
|
|
this.sysInfo = refreshData[0] || {};
|
|
this.healthData = refreshData[1] || {};
|
|
this.updateDashboard();
|
|
}, this));
|
|
}, this), 30);
|
|
|
|
return container;
|
|
},
|
|
|
|
renderHeader: function() {
|
|
var score = this.healthData.score || 0;
|
|
var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical'));
|
|
var scoreLabel = score >= 80 ? 'Excellent' : (score >= 60 ? 'Good' : (score >= 40 ? 'Warning' : 'Critical'));
|
|
|
|
return E('div', { 'class': 'sh-dashboard-header' }, [
|
|
E('div', { 'class': 'sh-dashboard-header-content' }, [
|
|
E('div', {}, [
|
|
E('h2', {}, '⚙️ System Control Center'),
|
|
E('p', { 'class': 'sh-dashboard-subtitle' }, 'System Monitoring & Management Center')
|
|
]),
|
|
E('div', { 'class': 'sh-dashboard-header-info' }, [
|
|
E('div', { 'class': 'sh-header-badge-group' }, [
|
|
E('span', { 'class': 'sh-dashboard-badge sh-dashboard-badge-version' },
|
|
'v0.3.2'),
|
|
E('span', { 'class': 'sh-dashboard-badge' },
|
|
'⏱️ ' + (this.sysInfo.uptime_formatted || '0d 0h 0m')),
|
|
E('span', { 'class': 'sh-dashboard-badge' },
|
|
'🖥️ ' + (this.sysInfo.hostname || 'OpenWrt'))
|
|
]),
|
|
this.renderHealthGauge(score, scoreClass, scoreLabel)
|
|
])
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderHealthGauge: function(score, scoreClass, scoreLabel) {
|
|
return E('div', { 'class': 'sh-health-gauge sh-health-' + scoreClass }, [
|
|
E('div', { 'class': 'sh-health-score' }, [
|
|
E('span', { 'class': 'sh-health-score-value' }, score),
|
|
E('span', { 'class': 'sh-health-score-max' }, '/100')
|
|
]),
|
|
E('div', { 'class': 'sh-health-label' }, scoreLabel),
|
|
E('div', { 'class': 'sh-health-progress' }, [
|
|
E('div', {
|
|
'class': 'sh-health-progress-fill sh-health-progress-' + scoreClass,
|
|
'style': 'width: ' + score + '%'
|
|
})
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderStatsOverview: function() {
|
|
var score = this.healthData.score || 0;
|
|
var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical'));
|
|
var scoreLabel = score >= 80 ? 'Excellent' : (score >= 60 ? 'Good' : (score >= 40 ? 'Warning' : 'Critical'));
|
|
|
|
// Enhanced stats with status indicators (v0.3.2)
|
|
var cpu = this.healthData.cpu || {};
|
|
var memory = this.healthData.memory || {};
|
|
var disk = this.healthData.disk || {};
|
|
var network = this.healthData.network || {};
|
|
|
|
// Process count (v0.3.2)
|
|
var processes = (cpu.processes_running || 0) + '/' + (cpu.processes_total || 0);
|
|
|
|
// Network throughput (v0.3.2) - format bytes
|
|
var rxGB = ((network.rx_bytes || 0) / 1024 / 1024 / 1024).toFixed(2);
|
|
var txGB = ((network.tx_bytes || 0) / 1024 / 1024 / 1024).toFixed(2);
|
|
|
|
// Status icons (v0.3.2)
|
|
var getStatusIcon = function(status) {
|
|
if (status === 'critical') return '⚠️';
|
|
if (status === 'warning') return '⚡';
|
|
return '✓';
|
|
};
|
|
|
|
return E('div', { 'class': 'sh-stats-overview-grid' }, [
|
|
// Health Score Card
|
|
E('div', { 'class': 'sh-stat-overview-card sh-stat-' + scoreClass }, [
|
|
this.renderHealthGauge(score, scoreClass, scoreLabel)
|
|
]),
|
|
|
|
// CPU Card with enhanced info
|
|
E('div', {
|
|
'class': 'sh-stat-overview-card sh-stat-cpu sh-stat-' + (cpu.status || 'ok'),
|
|
'title': 'Load: ' + (cpu.load_1m || '0') + ' | ' + (cpu.cores || 0) + ' cores | ' + processes + ' processes'
|
|
}, [
|
|
E('div', { 'class': 'sh-stat-overview-icon' }, '🔥'),
|
|
E('div', { 'class': 'sh-stat-overview-value' }, [
|
|
E('span', {}, (cpu.usage || 0) + '%'),
|
|
E('span', { 'class': 'sh-stat-status-icon' }, getStatusIcon(cpu.status))
|
|
]),
|
|
E('div', { 'class': 'sh-stat-overview-label' }, 'CPU Usage'),
|
|
E('div', { 'class': 'sh-stat-overview-detail' },
|
|
'Load: ' + (cpu.load_1m || '0') + ' • ' + processes + ' proc')
|
|
]),
|
|
|
|
// Memory Card with swap info
|
|
E('div', {
|
|
'class': 'sh-stat-overview-card sh-stat-memory sh-stat-' + (memory.status || 'ok'),
|
|
'title': ((memory.used_kb || 0) / 1024).toFixed(0) + ' MB / ' + ((memory.total_kb || 0) / 1024).toFixed(0) + ' MB' +
|
|
(memory.swap_total_kb > 0 ? ' | Swap: ' + (memory.swap_usage || 0) + '%' : '')
|
|
}, [
|
|
E('div', { 'class': 'sh-stat-overview-icon' }, '💾'),
|
|
E('div', { 'class': 'sh-stat-overview-value' }, [
|
|
E('span', {}, (memory.usage || 0) + '%'),
|
|
E('span', { 'class': 'sh-stat-status-icon' }, getStatusIcon(memory.status))
|
|
]),
|
|
E('div', { 'class': 'sh-stat-overview-label' }, 'Memory'),
|
|
E('div', { 'class': 'sh-stat-overview-detail' },
|
|
((memory.used_kb || 0) / 1024).toFixed(0) + 'MB / ' +
|
|
((memory.total_kb || 0) / 1024).toFixed(0) + 'MB' +
|
|
(memory.swap_total_kb > 0 ? ' • Swap: ' + (memory.swap_usage || 0) + '%' : ''))
|
|
]),
|
|
|
|
// Disk Card
|
|
E('div', {
|
|
'class': 'sh-stat-overview-card sh-stat-disk sh-stat-' + (disk.status || 'ok'),
|
|
'title': ((disk.used_kb || 0) / 1024 / 1024).toFixed(1) + ' GB / ' + ((disk.total_kb || 0) / 1024 / 1024).toFixed(1) + ' GB'
|
|
}, [
|
|
E('div', { 'class': 'sh-stat-overview-icon' }, '💿'),
|
|
E('div', { 'class': 'sh-stat-overview-value' }, [
|
|
E('span', {}, (disk.usage || 0) + '%'),
|
|
E('span', { 'class': 'sh-stat-status-icon' }, getStatusIcon(disk.status))
|
|
]),
|
|
E('div', { 'class': 'sh-stat-overview-label' }, 'Disk Usage'),
|
|
E('div', { 'class': 'sh-stat-overview-detail' },
|
|
((disk.used_kb || 0) / 1024 / 1024).toFixed(1) + 'GB / ' +
|
|
((disk.total_kb || 0) / 1024 / 1024).toFixed(1) + 'GB')
|
|
]),
|
|
|
|
// Network Card (v0.3.2 - NEW)
|
|
E('div', {
|
|
'class': 'sh-stat-overview-card sh-stat-network sh-stat-' + (network.wan_up ? 'ok' : 'error'),
|
|
'title': 'RX: ' + rxGB + ' GB | TX: ' + txGB + ' GB'
|
|
}, [
|
|
E('div', { 'class': 'sh-stat-overview-icon' }, '🌐'),
|
|
E('div', { 'class': 'sh-stat-overview-value' }, [
|
|
E('span', {}, network.wan_up ? 'Online' : 'Offline'),
|
|
E('span', { 'class': 'sh-stat-status-icon' }, network.wan_up ? '✓' : '✗')
|
|
]),
|
|
E('div', { 'class': 'sh-stat-overview-label' }, 'Network'),
|
|
E('div', { 'class': 'sh-stat-overview-detail' },
|
|
'↓ ' + rxGB + 'GB • ↑ ' + txGB + 'GB')
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderSystemInfoGrid: function() {
|
|
var self = this;
|
|
var cpu = this.healthData.cpu || {};
|
|
|
|
return E('div', {}, [
|
|
E('h3', { 'class': 'sh-section-title' }, 'System Information'),
|
|
E('div', { 'class': 'sh-system-info-grid' }, [
|
|
// Hostname card with edit button
|
|
E('div', { 'class': 'sh-info-grid-card' }, [
|
|
E('div', { 'class': 'sh-info-grid-header' }, [
|
|
E('span', { 'class': 'sh-info-grid-icon' }, '🏷️'),
|
|
E('span', { 'class': 'sh-info-grid-title' }, 'Hostname')
|
|
]),
|
|
E('div', { 'class': 'sh-info-grid-value' }, this.sysInfo.hostname || 'unknown'),
|
|
E('button', {
|
|
'class': 'sh-info-grid-action',
|
|
'click': function() {
|
|
ui.addNotification(null, E('p', 'Edit hostname feature coming soon'), 'info');
|
|
}
|
|
}, '✏️ Edit')
|
|
]),
|
|
|
|
// Uptime card
|
|
E('div', { 'class': 'sh-info-grid-card' }, [
|
|
E('div', { 'class': 'sh-info-grid-header' }, [
|
|
E('span', { 'class': 'sh-info-grid-icon' }, '⏱️'),
|
|
E('span', { 'class': 'sh-info-grid-title' }, 'Uptime')
|
|
]),
|
|
E('div', { 'class': 'sh-info-grid-value' }, this.sysInfo.uptime_formatted || '0d 0h 0m'),
|
|
E('div', { 'class': 'sh-info-grid-detail' }, 'System runtime')
|
|
]),
|
|
|
|
// Load Average card (monospace per prompt)
|
|
E('div', { 'class': 'sh-info-grid-card' }, [
|
|
E('div', { 'class': 'sh-info-grid-header' }, [
|
|
E('span', { 'class': 'sh-info-grid-icon' }, '📊'),
|
|
E('span', { 'class': 'sh-info-grid-title' }, 'Load Average')
|
|
]),
|
|
E('div', { 'class': 'sh-info-grid-value sh-monospace' },
|
|
(cpu.load_1m || '0.00') + ' / ' +
|
|
(cpu.load_5m || '0.00') + ' / ' +
|
|
(cpu.load_15m || '0.00')
|
|
),
|
|
E('div', { 'class': 'sh-info-grid-detail' }, '1m / 5m / 15m')
|
|
]),
|
|
|
|
// Kernel Version card with copy icon
|
|
E('div', { 'class': 'sh-info-grid-card' }, [
|
|
E('div', { 'class': 'sh-info-grid-header' }, [
|
|
E('span', { 'class': 'sh-info-grid-icon' }, '⚙️'),
|
|
E('span', { 'class': 'sh-info-grid-title' }, 'Kernel Version')
|
|
]),
|
|
E('div', { 'class': 'sh-info-grid-value sh-monospace' }, this.sysInfo.kernel || 'unknown'),
|
|
E('button', {
|
|
'class': 'sh-info-grid-action',
|
|
'click': function() {
|
|
var kernel = self.sysInfo.kernel || 'unknown';
|
|
navigator.clipboard.writeText(kernel).then(function() {
|
|
ui.addNotification(null, E('p', '✓ Copied to clipboard: ' + kernel), 'info');
|
|
});
|
|
}
|
|
}, '📋 Copy')
|
|
])
|
|
])
|
|
]);
|
|
},
|
|
|
|
// v0.3.2 - Modern Quick Status with histograms
|
|
renderQuickStatusIndicators: function() {
|
|
var network = this.healthData.network || {};
|
|
var services = this.healthData.services || {};
|
|
|
|
// Calculate status metrics
|
|
var internetUp = network.wan_up;
|
|
var internetPercent = internetUp ? 100 : 0;
|
|
var internetStatus = internetUp ? 'ok' : 'critical';
|
|
|
|
var dnsOk = network.dns_ok !== false;
|
|
var dnsPercent = dnsOk ? 100 : 0;
|
|
var dnsStatus = dnsOk ? 'ok' : 'critical';
|
|
|
|
// NTP - placeholder (would need backend)
|
|
var ntpPercent = 100;
|
|
var ntpStatus = 'ok';
|
|
|
|
// Firewall - calculate health based on rules
|
|
var fwRules = network.firewall_rules || 0;
|
|
var fwPercent = fwRules > 0 ? 100 : 0;
|
|
var fwStatus = fwRules > 0 ? 'ok' : 'warning';
|
|
|
|
return E('div', { 'class': 'sh-status-modern-grid' }, [
|
|
// Internet Connectivity (v0.3.2)
|
|
E('div', { 'class': 'sh-st-metric sh-st-metric-internet' }, [
|
|
E('div', { 'class': 'sh-st-header' }, [
|
|
E('div', { 'class': 'sh-st-title-group' }, [
|
|
E('span', { 'class': 'sh-st-icon' }, '🌐'),
|
|
E('span', { 'class': 'sh-st-title' }, 'Internet')
|
|
]),
|
|
E('div', { 'class': 'sh-st-value-group' }, [
|
|
E('span', { 'class': 'sh-st-value' }, internetUp ? 'Online' : 'Offline'),
|
|
E('span', { 'class': 'sh-st-badge sh-st-badge-' + internetStatus },
|
|
internetUp ? 'connected' : 'disconnected')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-st-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-st-progress-fill sh-st-gradient-internet',
|
|
'style': 'width: ' + internetPercent + '%'
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-st-details-grid' }, [
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'WAN IP'),
|
|
E('span', { 'class': 'sh-st-detail-value sh-monospace' },
|
|
network.wan_ip || 'N/A')
|
|
]),
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Gateway'),
|
|
E('span', { 'class': 'sh-st-detail-value sh-monospace' },
|
|
network.gateway || 'N/A')
|
|
]),
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Status'),
|
|
E('span', { 'class': 'sh-st-detail-value' },
|
|
internetUp ? '✓ Active' : '✗ Down')
|
|
])
|
|
])
|
|
]),
|
|
|
|
// DNS Resolution (v0.3.2)
|
|
E('div', { 'class': 'sh-st-metric sh-st-metric-dns' }, [
|
|
E('div', { 'class': 'sh-st-header' }, [
|
|
E('div', { 'class': 'sh-st-title-group' }, [
|
|
E('span', { 'class': 'sh-st-icon' }, '🔍'),
|
|
E('span', { 'class': 'sh-st-title' }, 'DNS')
|
|
]),
|
|
E('div', { 'class': 'sh-st-value-group' }, [
|
|
E('span', { 'class': 'sh-st-value' }, dnsOk ? 'Resolving' : 'Failed'),
|
|
E('span', { 'class': 'sh-st-badge sh-st-badge-' + dnsStatus },
|
|
dnsOk ? 'healthy' : 'error')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-st-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-st-progress-fill sh-st-gradient-dns',
|
|
'style': 'width: ' + dnsPercent + '%'
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-st-details-grid' }, [
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Primary DNS'),
|
|
E('span', { 'class': 'sh-st-detail-value sh-monospace' },
|
|
network.dns_primary || '8.8.8.8')
|
|
]),
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Secondary'),
|
|
E('span', { 'class': 'sh-st-detail-value sh-monospace' },
|
|
network.dns_secondary || '8.8.4.4')
|
|
]),
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Queries'),
|
|
E('span', { 'class': 'sh-st-detail-value' },
|
|
(network.dns_queries || 0) + ' / min')
|
|
])
|
|
])
|
|
]),
|
|
|
|
// NTP Sync (v0.3.2)
|
|
E('div', { 'class': 'sh-st-metric sh-st-metric-ntp' }, [
|
|
E('div', { 'class': 'sh-st-header' }, [
|
|
E('div', { 'class': 'sh-st-title-group' }, [
|
|
E('span', { 'class': 'sh-st-icon' }, '🕐'),
|
|
E('span', { 'class': 'sh-st-title' }, 'NTP Sync')
|
|
]),
|
|
E('div', { 'class': 'sh-st-value-group' }, [
|
|
E('span', { 'class': 'sh-st-value' }, 'Synced'),
|
|
E('span', { 'class': 'sh-st-badge sh-st-badge-' + ntpStatus }, 'active')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-st-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-st-progress-fill sh-st-gradient-ntp',
|
|
'style': 'width: ' + ntpPercent + '%'
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-st-details-grid' }, [
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Server'),
|
|
E('span', { 'class': 'sh-st-detail-value sh-monospace' },
|
|
network.ntp_server || 'pool.ntp.org')
|
|
]),
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Offset'),
|
|
E('span', { 'class': 'sh-st-detail-value' },
|
|
(network.ntp_offset || '0') + ' ms')
|
|
]),
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Last Sync'),
|
|
E('span', { 'class': 'sh-st-detail-value' },
|
|
network.ntp_last_sync || 'Just now')
|
|
])
|
|
])
|
|
]),
|
|
|
|
// Firewall Status (v0.3.2)
|
|
E('div', { 'class': 'sh-st-metric sh-st-metric-firewall' }, [
|
|
E('div', { 'class': 'sh-st-header' }, [
|
|
E('div', { 'class': 'sh-st-title-group' }, [
|
|
E('span', { 'class': 'sh-st-icon' }, '🛡️'),
|
|
E('span', { 'class': 'sh-st-title' }, 'Firewall')
|
|
]),
|
|
E('div', { 'class': 'sh-st-value-group' }, [
|
|
E('span', { 'class': 'sh-st-value' }, fwRules + ' rules'),
|
|
E('span', { 'class': 'sh-st-badge sh-st-badge-' + fwStatus },
|
|
fwRules > 0 ? 'active' : 'inactive')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-st-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-st-progress-fill sh-st-gradient-firewall',
|
|
'style': 'width: ' + fwPercent + '%'
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-st-details-grid' }, [
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Input'),
|
|
E('span', { 'class': 'sh-st-detail-value' },
|
|
network.fw_input || 'DROP')
|
|
]),
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Forward'),
|
|
E('span', { 'class': 'sh-st-detail-value' },
|
|
network.fw_forward || 'REJECT')
|
|
]),
|
|
E('div', { 'class': 'sh-st-detail' }, [
|
|
E('span', { 'class': 'sh-st-detail-label' }, 'Output'),
|
|
E('span', { 'class': 'sh-st-detail-value' },
|
|
network.fw_output || 'ACCEPT')
|
|
])
|
|
])
|
|
])
|
|
]);
|
|
},
|
|
|
|
// v0.3.2 - Modern real-time metrics with histograms
|
|
renderRealtimeMetrics: function() {
|
|
var cpu = this.healthData.cpu || {};
|
|
var memory = this.healthData.memory || {};
|
|
var disk = this.healthData.disk || {};
|
|
var temp = this.healthData.temperature || {};
|
|
var network = this.healthData.network || {};
|
|
var services = this.healthData.services || {};
|
|
|
|
return E('div', { 'class': 'sh-realtime-metrics' }, [
|
|
// CPU with load trend bars
|
|
E('div', { 'class': 'sh-rt-metric sh-rt-metric-cpu' }, [
|
|
E('div', { 'class': 'sh-rt-header' }, [
|
|
E('div', { 'class': 'sh-rt-title-group' }, [
|
|
E('span', { 'class': 'sh-rt-icon' }, '🔥'),
|
|
E('span', { 'class': 'sh-rt-title' }, 'CPU Performance')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-value-group' }, [
|
|
E('span', { 'class': 'sh-rt-value' }, (cpu.usage || 0) + '%'),
|
|
E('span', { 'class': 'sh-rt-badge sh-rt-badge-' + (cpu.status || 'ok') }, cpu.status || 'ok')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-rt-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-progress-fill sh-rt-gradient-cpu',
|
|
'style': 'width: ' + (cpu.usage || 0) + '%',
|
|
'data-value': (cpu.usage || 0)
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-rt-details-grid' }, [
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Load Average'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, (cpu.load_1m || '0.00'))
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Cores'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, (cpu.cores || 0))
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Processes'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, (cpu.processes_running || 0) + '/' + (cpu.processes_total || 0))
|
|
])
|
|
]),
|
|
// Mini histogram for load average
|
|
E('div', { 'class': 'sh-rt-histogram' },
|
|
this.renderLoadHistogram([cpu.load_1m || 0, cpu.load_5m || 0, cpu.load_15m || 0]))
|
|
]),
|
|
|
|
// Memory with swap visualization
|
|
E('div', { 'class': 'sh-rt-metric sh-rt-metric-memory' }, [
|
|
E('div', { 'class': 'sh-rt-header' }, [
|
|
E('div', { 'class': 'sh-rt-title-group' }, [
|
|
E('span', { 'class': 'sh-rt-icon' }, '💾'),
|
|
E('span', { 'class': 'sh-rt-title' }, 'Memory Usage')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-value-group' }, [
|
|
E('span', { 'class': 'sh-rt-value' }, (memory.usage || 0) + '%'),
|
|
E('span', { 'class': 'sh-rt-badge sh-rt-badge-' + (memory.status || 'ok') }, memory.status || 'ok')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-rt-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-progress-fill sh-rt-gradient-memory',
|
|
'style': 'width: ' + (memory.usage || 0) + '%',
|
|
'data-value': (memory.usage || 0)
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-rt-details-grid' }, [
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Used'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, ((memory.used_kb || 0) / 1024).toFixed(0) + ' MB')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Total'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, ((memory.total_kb || 0) / 1024).toFixed(0) + ' MB')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Swap'),
|
|
E('span', { 'class': 'sh-rt-detail-value' },
|
|
memory.swap_total_kb > 0 ? (memory.swap_usage || 0) + '%' : 'N/A')
|
|
])
|
|
]),
|
|
// Memory breakdown visualization
|
|
memory.swap_total_kb > 0 ? E('div', { 'class': 'sh-rt-multi-bar' }, [
|
|
E('div', { 'class': 'sh-rt-bar-segment sh-rt-bar-used', 'style': 'width: ' + (memory.usage || 0) + '%' }),
|
|
E('div', { 'class': 'sh-rt-bar-segment sh-rt-bar-swap', 'style': 'width: ' + (memory.swap_usage || 0) + '%' })
|
|
]) : null
|
|
]),
|
|
|
|
// Disk with storage breakdown
|
|
E('div', { 'class': 'sh-rt-metric sh-rt-metric-disk' }, [
|
|
E('div', { 'class': 'sh-rt-header' }, [
|
|
E('div', { 'class': 'sh-rt-title-group' }, [
|
|
E('span', { 'class': 'sh-rt-icon' }, '💿'),
|
|
E('span', { 'class': 'sh-rt-title' }, 'Disk Space')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-value-group' }, [
|
|
E('span', { 'class': 'sh-rt-value' }, (disk.usage || 0) + '%'),
|
|
E('span', { 'class': 'sh-rt-badge sh-rt-badge-' + (disk.status || 'ok') }, disk.status || 'ok')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-rt-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-progress-fill sh-rt-gradient-disk',
|
|
'style': 'width: ' + (disk.usage || 0) + '%',
|
|
'data-value': (disk.usage || 0)
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-rt-details-grid' }, [
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Used'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, ((disk.used_kb || 0) / 1024 / 1024).toFixed(1) + ' GB')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Total'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, ((disk.total_kb || 0) / 1024 / 1024).toFixed(1) + ' GB')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Free'),
|
|
E('span', { 'class': 'sh-rt-detail-value' },
|
|
((disk.total_kb - disk.used_kb || 0) / 1024 / 1024).toFixed(1) + ' GB')
|
|
])
|
|
])
|
|
]),
|
|
|
|
// Temperature gauge
|
|
E('div', { 'class': 'sh-rt-metric sh-rt-metric-temp' }, [
|
|
E('div', { 'class': 'sh-rt-header' }, [
|
|
E('div', { 'class': 'sh-rt-title-group' }, [
|
|
E('span', { 'class': 'sh-rt-icon' }, '🌡️'),
|
|
E('span', { 'class': 'sh-rt-title' }, 'Temperature')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-value-group' }, [
|
|
E('span', { 'class': 'sh-rt-value' }, (temp.value || 0) + '°C'),
|
|
E('span', { 'class': 'sh-rt-badge sh-rt-badge-' + (temp.status || 'ok') }, temp.status || 'ok')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-rt-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-progress-fill sh-rt-gradient-temp',
|
|
'style': 'width: ' + Math.min((temp.value || 0), 100) + '%',
|
|
'data-value': (temp.value || 0)
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-rt-details-grid' }, [
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Status'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, temp.status || 'ok')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Warning at'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, '70°C')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Critical at'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, '85°C')
|
|
])
|
|
])
|
|
]),
|
|
|
|
// Network Stats (v0.3.2 - NEW)
|
|
E('div', { 'class': 'sh-rt-metric sh-rt-metric-network' }, [
|
|
E('div', { 'class': 'sh-rt-header' }, [
|
|
E('div', { 'class': 'sh-rt-title-group' }, [
|
|
E('span', { 'class': 'sh-rt-icon' }, '🌐'),
|
|
E('span', { 'class': 'sh-rt-title' }, 'Network Stats')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-value-group' }, [
|
|
E('span', { 'class': 'sh-rt-value' }, network.wan_up ? 'Online' : 'Offline'),
|
|
E('span', { 'class': 'sh-rt-badge sh-rt-badge-' + (network.wan_up ? 'ok' : 'critical') },
|
|
network.wan_up ? 'connected' : 'disconnected')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-rt-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-progress-fill sh-rt-gradient-network',
|
|
'style': 'width: ' + (network.wan_up ? 100 : 0) + '%'
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-rt-details-grid' }, [
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Download'),
|
|
E('span', { 'class': 'sh-rt-detail-value' },
|
|
'↓ ' + ((network.rx_bytes || 0) / 1024 / 1024 / 1024).toFixed(2) + ' GB')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Upload'),
|
|
E('span', { 'class': 'sh-rt-detail-value' },
|
|
'↑ ' + ((network.tx_bytes || 0) / 1024 / 1024 / 1024).toFixed(2) + ' GB')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Status'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, network.status || 'unknown')
|
|
])
|
|
])
|
|
]),
|
|
|
|
// Services Monitor (v0.3.2 - NEW)
|
|
E('div', { 'class': 'sh-rt-metric sh-rt-metric-services' }, [
|
|
E('div', { 'class': 'sh-rt-header' }, [
|
|
E('div', { 'class': 'sh-rt-title-group' }, [
|
|
E('span', { 'class': 'sh-rt-icon' }, '⚙️'),
|
|
E('span', { 'class': 'sh-rt-title' }, 'Services Monitor')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-value-group' }, [
|
|
E('span', { 'class': 'sh-rt-value' }, (services.running || 0) + '/' + ((services.running || 0) + (services.failed || 0))),
|
|
E('span', {
|
|
'class': 'sh-rt-badge sh-rt-badge-' + ((services.failed || 0) === 0 ? 'ok' : (services.failed || 0) > 3 ? 'critical' : 'warning')
|
|
}, (services.failed || 0) === 0 ? 'healthy' : 'issues')
|
|
])
|
|
]),
|
|
E('div', { 'class': 'sh-rt-progress-modern' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-progress-fill sh-rt-gradient-services',
|
|
'style': 'width: ' + (((services.running || 0) + (services.failed || 0)) > 0
|
|
? Math.round(((services.running || 0) / ((services.running || 0) + (services.failed || 0))) * 100)
|
|
: 100) + '%'
|
|
})
|
|
]),
|
|
E('div', { 'class': 'sh-rt-details-grid' }, [
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Running'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, (services.running || 0) + ' services')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Failed'),
|
|
E('span', { 'class': 'sh-rt-detail-value' }, (services.failed || 0) + ' services')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-detail' }, [
|
|
E('span', { 'class': 'sh-rt-detail-label' }, 'Health'),
|
|
E('span', { 'class': 'sh-rt-detail-value' },
|
|
(((services.running || 0) + (services.failed || 0)) > 0
|
|
? Math.round(((services.running || 0) / ((services.running || 0) + (services.failed || 0))) * 100)
|
|
: 100) + '%')
|
|
])
|
|
])
|
|
])
|
|
]);
|
|
},
|
|
|
|
// Render mini histogram for load average
|
|
renderLoadHistogram: function(loads) {
|
|
var maxLoad = Math.max(...loads, 1);
|
|
return E('div', { 'class': 'sh-rt-histogram-bars' }, [
|
|
E('div', { 'class': 'sh-rt-histogram-bar-group' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-histogram-bar',
|
|
'style': 'height: ' + ((loads[0] / maxLoad) * 100) + '%',
|
|
'title': '1m: ' + loads[0]
|
|
}),
|
|
E('span', { 'class': 'sh-rt-histogram-label' }, '1m')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-histogram-bar-group' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-histogram-bar',
|
|
'style': 'height: ' + ((loads[1] / maxLoad) * 100) + '%',
|
|
'title': '5m: ' + loads[1]
|
|
}),
|
|
E('span', { 'class': 'sh-rt-histogram-label' }, '5m')
|
|
]),
|
|
E('div', { 'class': 'sh-rt-histogram-bar-group' }, [
|
|
E('div', {
|
|
'class': 'sh-rt-histogram-bar',
|
|
'style': 'height: ' + ((loads[2] / maxLoad) * 100) + '%',
|
|
'title': '15m: ' + loads[2]
|
|
}),
|
|
E('span', { 'class': 'sh-rt-histogram-label' }, '15m')
|
|
])
|
|
]);
|
|
},
|
|
|
|
getMetricConfig: function(type, data) {
|
|
switch(type) {
|
|
case 'CPU':
|
|
return {
|
|
icon: '🔥',
|
|
title: 'CPU Usage',
|
|
value: (data.usage || 0) + '%',
|
|
percentage: data.usage || 0,
|
|
status: data.status || 'ok',
|
|
color: this.getStatusColor(data.usage || 0),
|
|
details: 'Load: ' + (data.load_1m || '0') + ' • ' + (data.cores || 0) + ' cores'
|
|
};
|
|
case 'Memory':
|
|
var usedMB = ((data.used_kb || 0) / 1024).toFixed(0);
|
|
var totalMB = ((data.total_kb || 0) / 1024).toFixed(0);
|
|
return {
|
|
icon: '💾',
|
|
title: 'Memory',
|
|
value: (data.usage || 0) + '%',
|
|
percentage: data.usage || 0,
|
|
status: data.status || 'ok',
|
|
color: this.getStatusColor(data.usage || 0),
|
|
details: usedMB + ' MB / ' + totalMB + ' MB used'
|
|
};
|
|
case 'Disk':
|
|
var usedGB = ((data.used_kb || 0) / 1024 / 1024).toFixed(1);
|
|
var totalGB = ((data.total_kb || 0) / 1024 / 1024).toFixed(1);
|
|
return {
|
|
icon: '💿',
|
|
title: 'Disk Space',
|
|
value: (data.usage || 0) + '%',
|
|
percentage: data.usage || 0,
|
|
status: data.status || 'ok',
|
|
color: this.getStatusColor(data.usage || 0),
|
|
details: usedGB + ' GB / ' + totalGB + ' GB used'
|
|
};
|
|
case 'Temperature':
|
|
return {
|
|
icon: '🌡️',
|
|
title: 'Temperature',
|
|
value: (data.value || 0) + '°C',
|
|
percentage: Math.min((data.value || 0), 100),
|
|
status: data.status || 'ok',
|
|
color: this.getTempColor(data.value || 0),
|
|
details: 'Status: ' + (data.status || 'unknown')
|
|
};
|
|
default:
|
|
return {
|
|
icon: '📊',
|
|
title: type,
|
|
value: 'N/A',
|
|
percentage: 0,
|
|
status: 'unknown',
|
|
color: '#64748b',
|
|
details: 'No data'
|
|
};
|
|
}
|
|
},
|
|
|
|
getStatusColor: function(usage) {
|
|
if (usage >= 90) return '#ef4444';
|
|
if (usage >= 75) return '#f59e0b';
|
|
if (usage >= 50) return '#3b82f6';
|
|
return '#22c55e';
|
|
},
|
|
|
|
getTempColor: function(temp) {
|
|
if (temp >= 80) return '#ef4444';
|
|
if (temp >= 70) return '#f59e0b';
|
|
if (temp >= 60) return '#3b82f6';
|
|
return '#22c55e';
|
|
},
|
|
|
|
renderInfoCard: function(title, content) {
|
|
return E('div', { 'class': 'sh-info-card' }, [
|
|
E('div', { 'class': 'sh-info-card-header' }, [
|
|
E('h3', {}, title)
|
|
]),
|
|
E('div', { 'class': 'sh-info-card-body' }, content)
|
|
]);
|
|
},
|
|
|
|
renderSystemInfo: function() {
|
|
return E('div', { 'class': 'sh-info-list' }, [
|
|
this.renderInfoRow('🏷️', 'Hostname', this.sysInfo.hostname || 'unknown'),
|
|
this.renderInfoRow('🖥️', 'Model', this.sysInfo.model || 'Unknown'),
|
|
this.renderInfoRow('📦', 'OpenWrt', this.sysInfo.openwrt_version || 'Unknown'),
|
|
this.renderInfoRow('⚙️', 'Kernel', this.sysInfo.kernel || 'unknown'),
|
|
this.renderInfoRow('⏱️', 'Uptime', this.sysInfo.uptime_formatted || '0d 0h 0m'),
|
|
this.renderInfoRow('🕐', 'Local Time', this.sysInfo.local_time || 'unknown')
|
|
]);
|
|
},
|
|
|
|
renderNetworkInfo: function() {
|
|
var wan_status = this.healthData.network ? this.healthData.network.wan_up : false;
|
|
return E('div', { 'class': 'sh-info-list' }, [
|
|
this.renderInfoRow('🌐', 'WAN Status',
|
|
E('span', {
|
|
'class': 'sh-status-badge sh-status-' + (wan_status ? 'ok' : 'error')
|
|
}, wan_status ? 'Connected' : 'Disconnected')
|
|
),
|
|
this.renderInfoRow('📡', 'Network', this.healthData.network ? this.healthData.network.status : 'unknown')
|
|
]);
|
|
},
|
|
|
|
renderServicesInfo: function() {
|
|
var running = this.healthData.services ? this.healthData.services.running : 0;
|
|
var failed = this.healthData.services ? this.healthData.services.failed : 0;
|
|
|
|
return E('div', { 'class': 'sh-info-list' }, [
|
|
this.renderInfoRow('▶️', 'Running Services',
|
|
E('span', { 'class': 'sh-status-badge sh-status-ok' }, running + ' services')
|
|
),
|
|
this.renderInfoRow('⏹️', 'Failed Services',
|
|
failed > 0
|
|
? E('span', { 'class': 'sh-status-badge sh-status-error' }, failed + ' services')
|
|
: E('span', { 'class': 'sh-status-badge sh-status-ok' }, 'None')
|
|
),
|
|
this.renderInfoRow('🔗', 'Quick Actions',
|
|
E('a', {
|
|
'class': 'sh-link-button',
|
|
'href': '/cgi-bin/luci/admin/secubox/system/system-hub/services'
|
|
}, 'Manage Services →')
|
|
)
|
|
]);
|
|
},
|
|
|
|
renderInfoRow: function(icon, label, value) {
|
|
return E('div', { 'class': 'sh-info-row' }, [
|
|
E('span', { 'class': 'sh-info-icon' }, icon),
|
|
E('span', { 'class': 'sh-info-label' }, label),
|
|
E('span', { 'class': 'sh-info-value' }, value)
|
|
]);
|
|
},
|
|
|
|
updateDashboard: function() {
|
|
// Update real-time metrics (v0.3.2)
|
|
var realtimeMetrics = document.querySelector('.sh-realtime-metrics');
|
|
if (realtimeMetrics) {
|
|
var newMetrics = this.renderRealtimeMetrics();
|
|
dom.content(realtimeMetrics, Array.prototype.slice.call(newMetrics.childNodes));
|
|
}
|
|
|
|
// Update stats overview
|
|
var statsOverview = document.querySelector('.sh-stats-overview-grid');
|
|
if (statsOverview) {
|
|
var newStats = this.renderStatsOverview();
|
|
dom.content(statsOverview, Array.prototype.slice.call(newStats.childNodes));
|
|
}
|
|
|
|
// Update system info grid
|
|
var systemInfoGrid = document.querySelector('.sh-system-info-grid');
|
|
if (systemInfoGrid) {
|
|
var newInfoGrid = this.renderSystemInfoGrid();
|
|
var gridElement = newInfoGrid.querySelector('.sh-system-info-grid');
|
|
if (gridElement) {
|
|
dom.content(systemInfoGrid, Array.prototype.slice.call(gridElement.childNodes));
|
|
}
|
|
}
|
|
|
|
// Update quick status indicators
|
|
var statusIndicators = document.querySelector('.sh-status-indicators-grid');
|
|
if (statusIndicators) {
|
|
var newIndicators = this.renderQuickStatusIndicators();
|
|
dom.content(statusIndicators, Array.prototype.slice.call(newIndicators.childNodes));
|
|
}
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|