'use strict'; 'require view'; 'require ui'; 'require dom'; 'require poll'; 'require system-hub/api as API'; 'require system-hub/theme as Theme'; return view.extend({ healthData: null, sysInfo: null, load: function() { return Promise.all([ API.getSystemInfo(), API.getHealth(), Theme.getTheme() ]); }, render: function(data) { var self = this; this.sysInfo = data[0] || {}; this.healthData = data[1] || {}; var theme = data[2]; var container = E('div', { 'class': 'system-hub-dashboard' }, [ 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')); 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) ]) ]) ]); }, 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 });