'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('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')) ]) ]) ]); }, 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 }, [ E('div', { 'class': 'sh-stat-overview-value' }, score), E('div', { 'class': 'sh-stat-overview-label' }, 'Health Score'), E('div', { 'class': 'sh-stat-overview-status' }, 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') ]) ]) ]); }, renderQuickStatusIndicators: function() { var network = this.healthData.network || {}; var services = this.healthData.services || {}; // Determine status colors and icons var internetStatus = network.wan_up ? 'ok' : 'error'; var internetIcon = network.wan_up ? '✓' : '✗'; var internetText = network.wan_up ? 'Connected' : 'Disconnected'; var dnsStatus = network.dns_ok !== false ? 'ok' : 'error'; var dnsIcon = network.dns_ok !== false ? '✓' : '✗'; var dnsText = network.dns_ok !== false ? 'Resolving' : 'Failed'; var ntpStatus = 'ok'; // Placeholder, would need backend support var ntpIcon = '✓'; var ntpText = 'Synced'; var fwStatus = 'ok'; var fwIcon = '✓'; var fwText = (network.firewall_rules || 0) + ' rules active'; return E('div', { 'class': 'sh-status-indicators-grid' }, [ // Internet connectivity E('div', { 'class': 'sh-status-indicator sh-status-' + internetStatus }, [ E('div', { 'class': 'sh-status-indicator-icon' }, '🌐'), E('div', { 'class': 'sh-status-indicator-content' }, [ E('div', { 'class': 'sh-status-indicator-label' }, 'Internet Connectivity'), E('div', { 'class': 'sh-status-indicator-value' }, [ E('span', { 'class': 'sh-status-badge sh-status-badge-' + internetStatus }, internetIcon), E('span', {}, internetText) ]) ]) ]), // DNS resolution E('div', { 'class': 'sh-status-indicator sh-status-' + dnsStatus }, [ E('div', { 'class': 'sh-status-indicator-icon' }, '🔍'), E('div', { 'class': 'sh-status-indicator-content' }, [ E('div', { 'class': 'sh-status-indicator-label' }, 'DNS Resolution'), E('div', { 'class': 'sh-status-indicator-value' }, [ E('span', { 'class': 'sh-status-badge sh-status-badge-' + dnsStatus }, dnsIcon), E('span', {}, dnsText) ]) ]) ]), // NTP sync E('div', { 'class': 'sh-status-indicator sh-status-' + ntpStatus }, [ E('div', { 'class': 'sh-status-indicator-icon' }, '🕐'), E('div', { 'class': 'sh-status-indicator-content' }, [ E('div', { 'class': 'sh-status-indicator-label' }, 'NTP Sync'), E('div', { 'class': 'sh-status-indicator-value' }, [ E('span', { 'class': 'sh-status-badge sh-status-badge-' + ntpStatus }, ntpIcon), E('span', {}, ntpText) ]) ]) ]), // Firewall status E('div', { 'class': 'sh-status-indicator sh-status-' + fwStatus }, [ E('div', { 'class': 'sh-status-indicator-icon' }, 'đŸ›Ąī¸'), E('div', { 'class': 'sh-status-indicator-content' }, [ E('div', { 'class': 'sh-status-indicator-label' }, 'Firewall'), E('div', { 'class': 'sh-status-indicator-value' }, [ E('span', { 'class': 'sh-status-badge sh-status-badge-' + fwStatus }, fwIcon), E('span', {}, fwText) ]) ]) ]) ]); }, // 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 || {}; 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') ]) ]) ]) ]); }, // 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) { dom.content(realtimeMetrics, this.renderRealtimeMetrics().children); } // Update stats overview var statsOverview = document.querySelector('.sh-stats-overview-grid'); if (statsOverview) { dom.content(statsOverview, this.renderStatsOverview().children); } // Update system info grid var systemInfoGrid = document.querySelector('.sh-system-info-grid'); if (systemInfoGrid) { dom.content(systemInfoGrid, this.renderSystemInfoGrid().querySelector('.sh-system-info-grid').children); } // Update quick status indicators var statusIndicators = document.querySelector('.sh-status-indicators-grid'); if (statusIndicators) { dom.content(statusIndicators, this.renderQuickStatusIndicators().children); } }, handleSaveApply: null, handleSave: null, handleReset: null });