'use strict'; 'require view'; 'require poll'; 'require system-hub/api as API'; return L.view.extend({ load: function() { return Promise.all([ API.getSystemInfo(), API.getHealth(), API.getStatus() ]); }, render: function(data) { var sysInfo = data[0] || {}; var health = data[1] || {}; var status = data[2] || {}; var v = E('div', { 'class': 'cbi-map' }, [ E('h2', {}, _('System Hub - Overview')), E('div', { 'class': 'cbi-map-descr' }, _('Central system control and monitoring')) ]); // System Information Card var infoSection = E('div', { 'class': 'cbi-section' }, [ E('h3', {}, _('System Information')), E('div', { 'class': 'table' }, [ E('div', { 'class': 'tr' }, [ E('div', { 'class': 'td left', 'width': '50%' }, [ E('strong', {}, _('Hostname: ')), E('span', {}, sysInfo.hostname || 'unknown') ]), E('div', { 'class': 'td left', 'width': '50%' }, [ E('strong', {}, _('Model: ')), E('span', {}, sysInfo.model || 'Unknown') ]) ]), E('div', { 'class': 'tr' }, [ E('div', { 'class': 'td left', 'width': '50%' }, [ E('strong', {}, _('OpenWrt: ')), E('span', {}, sysInfo.openwrt_version || 'Unknown') ]), E('div', { 'class': 'td left', 'width': '50%' }, [ E('strong', {}, _('Kernel: ')), E('span', {}, sysInfo.kernel || 'unknown') ]) ]), E('div', { 'class': 'tr' }, [ E('div', { 'class': 'td left', 'width': '50%' }, [ E('strong', {}, _('Uptime: ')), E('span', {}, sysInfo.uptime_formatted || '0d 0h 0m') ]), E('div', { 'class': 'td left', 'width': '50%' }, [ E('strong', {}, _('Local Time: ')), E('span', {}, sysInfo.local_time || 'unknown') ]) ]) ]) ]); v.appendChild(infoSection); // Health Metrics with Gauges var healthSection = E('div', { 'class': 'cbi-section' }, [ E('h3', {}, _('System Health')) ]); var gaugesContainer = E('div', { 'style': 'display: flex; justify-content: space-around; flex-wrap: wrap; margin: 20px 0;' }); // CPU Load Gauge var cpuLoad = parseFloat(health.load ? health.load['1min'] : status.health ? status.health.cpu_load : '0'); var cpuPercent = Math.min((cpuLoad * 100 / (health.cpu ? health.cpu.cores : 1)), 100); gaugesContainer.appendChild(this.createGauge('CPU Load', cpuPercent, cpuLoad.toFixed(2))); // Memory Gauge var memPercent = health.memory ? health.memory.percent : (status.health ? status.health.mem_percent : 0); var memUsed = health.memory ? (health.memory.used_kb / 1024).toFixed(0) : 0; var memTotal = health.memory ? (health.memory.total_kb / 1024).toFixed(0) : 0; gaugesContainer.appendChild(this.createGauge('Memory', memPercent, memUsed + ' / ' + memTotal + ' MB')); // Disk Gauge var diskPercent = status.disk_percent || 0; var diskInfo = ''; if (health.storage && health.storage.length > 0) { var root = health.storage.find(function(s) { return s.mountpoint === '/'; }); if (root) { diskPercent = root.percent; diskInfo = root.used + ' / ' + root.size; } } gaugesContainer.appendChild(this.createGauge('Disk Usage', diskPercent, diskInfo || diskPercent + '%')); healthSection.appendChild(gaugesContainer); v.appendChild(healthSection); // CPU Info if (health.cpu) { var cpuSection = E('div', { 'class': 'cbi-section' }, [ E('h3', {}, _('CPU Information')), E('div', { 'class': 'table' }, [ E('div', { 'class': 'tr' }, [ E('div', { 'class': 'td left', 'width': '50%' }, [ E('strong', {}, _('Model: ')), E('span', {}, health.cpu.model) ]), E('div', { 'class': 'td left', 'width': '50%' }, [ E('strong', {}, _('Cores: ')), E('span', {}, String(health.cpu.cores)) ]) ]), E('div', { 'class': 'tr' }, [ E('div', { 'class': 'td left' }, [ E('strong', {}, _('Load Average: ')), E('span', {}, (health.load ? health.load['1min'] + ' / ' + health.load['5min'] + ' / ' + health.load['15min'] : 'N/A')) ]) ]) ]) ]); v.appendChild(cpuSection); } // Temperature if (health.temperatures && health.temperatures.length > 0) { var tempSection = E('div', { 'class': 'cbi-section' }, [ E('h3', {}, _('Temperature')) ]); var tempTable = E('table', { 'class': 'table' }, [ E('tr', { 'class': 'tr table-titles' }, [ E('th', { 'class': 'th' }, _('Zone')), E('th', { 'class': 'th' }, _('Temperature')) ]) ]); health.temperatures.forEach(function(temp) { var color = temp.celsius > 80 ? 'red' : (temp.celsius > 60 ? 'orange' : 'green'); tempTable.appendChild(E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td' }, temp.zone), E('td', { 'class': 'td' }, [ E('span', { 'style': 'color: ' + color + '; font-weight: bold;' }, temp.celsius + '°C') ]) ])); }); tempSection.appendChild(tempTable); v.appendChild(tempSection); } // Storage if (health.storage && health.storage.length > 0) { var storageSection = E('div', { 'class': 'cbi-section' }, [ E('h3', {}, _('Storage')) ]); var storageTable = E('table', { 'class': 'table' }, [ E('tr', { 'class': 'tr table-titles' }, [ E('th', { 'class': 'th' }, _('Mountpoint')), E('th', { 'class': 'th' }, _('Filesystem')), E('th', { 'class': 'th' }, _('Size')), E('th', { 'class': 'th' }, _('Used')), E('th', { 'class': 'th' }, _('Available')), E('th', { 'class': 'th' }, _('Use %')) ]) ]); health.storage.forEach(function(storage) { var color = storage.percent > 90 ? 'red' : (storage.percent > 75 ? 'orange' : 'green'); storageTable.appendChild(E('tr', { 'class': 'tr' }, [ E('td', { 'class': 'td' }, E('strong', {}, storage.mountpoint)), E('td', { 'class': 'td' }, E('code', {}, storage.filesystem)), E('td', { 'class': 'td' }, storage.size), E('td', { 'class': 'td' }, storage.used), E('td', { 'class': 'td' }, storage.available), E('td', { 'class': 'td' }, [ E('div', { 'style': 'display: flex; align-items: center;' }, [ E('div', { 'style': 'flex: 1; background: #eee; height: 10px; border-radius: 5px; margin-right: 10px;' }, [ E('div', { 'style': 'background: ' + color + '; width: ' + storage.percent + '%; height: 100%; border-radius: 5px;' }) ]), E('span', {}, storage.percent + '%') ]) ]) ])); }); storageSection.appendChild(storageTable); v.appendChild(storageSection); } // Auto-refresh every 5 seconds poll.add(L.bind(function() { return Promise.all([ API.getHealth(), API.getStatus() ]).then(L.bind(function(refreshData) { // Update would go here in a production implementation }, this)); }, this), 5); return v; }, createGauge: function(label, percent, detail) { var color = percent > 90 ? '#dc3545' : (percent > 75 ? '#fd7e14' : '#28a745'); var size = 120; var strokeWidth = 10; var radius = (size - strokeWidth) / 2; var circumference = 2 * Math.PI * radius; var offset = circumference - (percent / 100 * circumference); return E('div', { 'style': 'text-align: center; margin: 10px;' }, [ E('div', {}, [ E('svg', { 'width': size, 'height': size, 'style': 'transform: rotate(-90deg);' }, [ E('circle', { 'cx': size/2, 'cy': size/2, 'r': radius, 'fill': 'none', 'stroke': '#eee', 'stroke-width': strokeWidth }), E('circle', { 'cx': size/2, 'cy': size/2, 'r': radius, 'fill': 'none', 'stroke': color, 'stroke-width': strokeWidth, 'stroke-dasharray': circumference, 'stroke-dashoffset': offset, 'stroke-linecap': 'round' }) ]) ]), E('div', { 'style': 'margin-top: -' + (size/2 + 10) + 'px; font-size: 20px; font-weight: bold; color: ' + color + ';' }, Math.round(percent) + '%'), E('div', { 'style': 'margin-top: ' + (size/2 - 10) + 'px; font-weight: bold;' }, label), E('div', { 'style': 'font-size: 12px; color: #666;' }, detail) ]); }, handleSaveApply: null, handleSave: null, handleReset: null });