'use strict'; 'require view'; 'require ui'; 'require dom'; 'require poll'; 'require secubox/api as API'; 'require secubox-theme/theme as Theme'; 'require secubox/nav as SecuNav'; // Load theme resources document.head.appendChild(E('link', { 'rel': 'stylesheet', 'type': 'text/css', 'href': L.resource('secubox-theme/secubox-theme.css') })); document.head.appendChild(E('link', { 'rel': 'stylesheet', 'type': 'text/css', 'href': L.resource('secubox-theme/themes/cyberpunk.css') })); // Respect LuCI language/theme preferences var secuLang = (typeof L !== 'undefined' && L.env && L.env.lang) || (document.documentElement && document.documentElement.getAttribute('lang')) || (navigator.language ? navigator.language.split('-')[0] : 'en'); Theme.init({ language: secuLang }); return view.extend({ cpuHistory: [], memoryHistory: [], diskHistory: [], networkHistory: [], maxDataPoints: 60, latestHealth: {}, load: function() { return this.refreshData(); }, refreshData: function() { var self = this; return API.getSystemHealth().then(function(data) { var health = data || {}; self.latestHealth = health; self.addDataPoint(health); return health; }); }, addDataPoint: function(health) { var timestamp = Date.now(); this.cpuHistory.push({ time: timestamp, value: (health.cpu && health.cpu.percent) || 0 }); this.memoryHistory.push({ time: timestamp, value: (health.memory && health.memory.percent) || 0 }); this.diskHistory.push({ time: timestamp, value: (health.disk && health.disk.percent) || 0 }); var netRx = (health.network && health.network.rx_bytes) || 0; var netTx = (health.network && health.network.tx_bytes) || 0; this.networkHistory.push({ time: timestamp, rx: netRx, tx: netTx }); ['cpuHistory', 'memoryHistory', 'diskHistory', 'networkHistory'].forEach(function(key) { if (this[key].length > this.maxDataPoints) this[key].shift(); }, this); }, render: function() { var container = E('div', { 'class': 'secubox-monitoring-page' }, [ E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/core/variables.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/monitoring.css') }), SecuNav.renderTabs('monitoring'), this.renderHeader(), this.renderChartsGrid(), this.renderCurrentStatsCard() ]); this.updateCharts(); this.updateCurrentStats(); var self = this; poll.add(function() { return self.refreshData().then(function() { self.updateCharts(); self.updateCurrentStats(); }); }, 5); this.hideLegacyTabMenu(); return container; }, renderHeader: function() { var snapshot = this.getLatestSnapshot(); var rates = this.getNetworkRateSummary(); return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ E('h2', { 'class': 'sh-page-title' }, [ E('span', { 'class': 'sh-page-title-icon' }, '๐Ÿ“ก'), _('SecuBox Monitoring') ]), E('p', { 'class': 'sh-page-subtitle' }, _('Live telemetry for CPU, memory, storage, and network throughput')) ]), E('div', { 'class': 'sh-header-meta' }, [ this.renderHeaderChip('cpu', '๐Ÿ”ฅ', _('CPU'), snapshot.cpu.value.toFixed(1) + '%', snapshot.cpu.value), this.renderHeaderChip('memory', '๐Ÿ’พ', _('Memory'), snapshot.memory.value.toFixed(1) + '%', snapshot.memory.value), this.renderHeaderChip('disk', '๐Ÿ’ฟ', _('Disk'), snapshot.disk.value.toFixed(1) + '%', snapshot.disk.value), this.renderHeaderChip('uptime', 'โฑ', _('Uptime'), API.formatUptime(snapshot.uptime || 0)), this.renderHeaderChip('net', '๐ŸŒ', _('Network'), rates.summary) ]) ]); }, renderHeaderChip: function(id, icon, label, value, percent) { return E('div', { 'class': 'sh-header-chip' }, [ E('span', { 'class': 'sh-chip-icon' }, icon), E('div', { 'class': 'sh-chip-text' }, [ E('span', { 'class': 'sh-chip-label' }, label), E('strong', { 'id': 'secubox-monitoring-chip-' + id, 'style': (typeof percent === 'number') ? ('color:' + this.getColorForValue(percent)) : '' }, value) ]) ]); }, renderChartsGrid: function() { return E('section', { 'class': 'secubox-charts-grid' }, [ this.renderChartCard('cpu', _('CPU Usage'), '%', '#6366f1'), this.renderChartCard('memory', _('Memory Usage'), '%', '#22c55e'), this.renderChartCard('disk', _('Disk Usage'), '%', '#f59e0b'), this.renderChartCard('network', _('Network Throughput'), 'B/s', '#3b82f6') ]); }, renderChartCard: function(type, title, unit, accent) { return E('div', { 'class': 'secubox-chart-card' }, [ E('h3', { 'class': 'secubox-chart-title' }, title), E('div', { 'class': 'secubox-chart-container' }, E('svg', { 'id': 'chart-' + type, 'class': 'secubox-chart', 'viewBox': '0 0 600 200', 'preserveAspectRatio': 'none', 'data-accent': accent }) ), E('div', { 'class': 'secubox-chart-legend' }, [ E('span', { 'id': 'current-' + type, 'class': 'secubox-current-value' }, '0' + unit), E('span', { 'class': 'secubox-chart-unit' }, _('Live')) ]) ]); }, renderCurrentStatsCard: function() { return E('section', { 'class': 'sb-card' }, [ E('div', { 'class': 'sb-card-header' }, [ E('h2', {}, _('Current Statistics')), E('p', { 'class': 'sb-card-subtitle' }, _('Real-time snapshot of key resources')) ]), E('div', { 'id': 'current-stats', 'class': 'secubox-stats-table' }, this.renderStatsTable()) ]); }, renderStatsTable: function() { var snapshot = this.getLatestSnapshot(); var rates = this.getNetworkRateSummary(); var load = (this.latestHealth.load && this.latestHealth.load[0]) || '0.00'; var stats = [ { label: _('CPU Usage'), value: snapshot.cpu.value.toFixed(1) + '%', icon: 'โšก' }, { label: _('Memory Usage'), value: snapshot.memory.value.toFixed(1) + '%', icon: '๐Ÿ’พ' }, { label: _('Disk Usage'), value: snapshot.disk.value.toFixed(1) + '%', icon: '๐Ÿ’ฟ' }, { label: _('System Load (1m)'), value: load, icon: '๐Ÿ“ˆ' }, { label: _('Network RX/TX'), value: rates.summary, icon: '๐ŸŒ' }, { label: _('Data Window'), value: this.cpuHistory.length + ' / ' + this.maxDataPoints, icon: '๐Ÿ•’' } ]; return stats.map(function(stat) { return E('div', { 'class': 'secubox-stat-item' }, [ E('span', { 'class': 'secubox-stat-icon' }, stat.icon), E('div', { 'class': 'secubox-stat-details' }, [ E('div', { 'class': 'secubox-stat-label' }, stat.label), E('div', { 'class': 'secubox-stat-value' }, stat.value) ]) ]); }); }, updateCharts: function() { this.drawChart('cpu', this.cpuHistory, '#6366f1'); this.drawChart('memory', this.memoryHistory, '#22c55e'); this.drawChart('disk', this.diskHistory, '#f59e0b'); this.drawNetworkChart(); }, drawChart: function(type, data, color) { var svg = document.getElementById('chart-' + type); var currentEl = document.getElementById('current-' + type); if (!svg || data.length === 0) return; var width = 600; var height = 200; var padding = 12; var values = data.map(function(d) { return d.value; }); var maxValue = Math.max(100, Math.max.apply(Math, values)); var minValue = 0; var pathPoints = data.map(function(point, idx) { var x = padding + (width - 2 * padding) * (idx / Math.max(1, this.maxDataPoints - 1)); var y = height - padding - ((point.value - minValue) / (maxValue - minValue)) * (height - 2 * padding); return x + ',' + y; }, this).join(' '); svg.innerHTML = ''; svg.appendChild(E('polyline', { 'points': pathPoints, 'fill': 'none', 'stroke': color, 'stroke-width': '2', 'stroke-linejoin': 'round', 'stroke-linecap': 'round' })); if (currentEl) { currentEl.textContent = (data[data.length - 1].value || 0).toFixed(1) + '%'; } }, drawNetworkChart: function() { var svg = document.getElementById('chart-network'); var currentEl = document.getElementById('current-network'); if (!svg || this.networkHistory.length < 2) return; var width = 600; var height = 200; var padding = 12; var rates = this.networkHistory.slice(1).map(function(point, idx) { var prev = this.networkHistory[idx]; var seconds = Math.max(1, (point.time - prev.time) / 1000); return { time: point.time, rx: Math.max(0, (point.rx - prev.rx) / seconds), tx: Math.max(0, (point.tx - prev.tx) / seconds) }; }, this); var maxRate = Math.max(1024, Math.max.apply(Math, rates.map(function(r) { return Math.max(r.rx, r.tx); }))); function buildPoints(fn) { return rates.map(function(point, idx) { var x = padding + (width - 2 * padding) * (idx / Math.max(1, rates.length - 1)); var y = height - padding - (fn(point) / maxRate) * (height - 2 * padding); return x + ',' + y; }).join(' '); } svg.innerHTML = ''; svg.appendChild(E('polyline', { 'points': buildPoints(function(p) { return p.rx; }), 'fill': 'none', 'stroke': '#22c55e', 'stroke-width': '2' })); svg.appendChild(E('polyline', { 'points': buildPoints(function(p) { return p.tx; }), 'fill': 'none', 'stroke': '#3b82f6', 'stroke-width': '2' })); if (currentEl) { var last = rates[rates.length - 1]; currentEl.textContent = API.formatBytes(last.rx + last.tx) + '/s'; } }, updateCurrentStats: function() { var statsContainer = document.getElementById('current-stats'); if (statsContainer) dom.content(statsContainer, this.renderStatsTable()); var snapshot = this.getLatestSnapshot(); this.updateHeaderChips(snapshot); }, updateHeaderChips: function(snapshot) { this.setHeaderChipValue('cpu', snapshot.cpu.value.toFixed(1) + '%', snapshot.cpu.value); this.setHeaderChipValue('memory', snapshot.memory.value.toFixed(1) + '%', snapshot.memory.value); this.setHeaderChipValue('disk', snapshot.disk.value.toFixed(1) + '%', snapshot.disk.value); this.setHeaderChipValue('uptime', API.formatUptime(snapshot.uptime || 0)); var rates = this.getNetworkRateSummary(); this.setHeaderChipValue('net', rates.summary); }, setHeaderChipValue: function(id, text, percent) { var target = document.getElementById('secubox-monitoring-chip-' + id); if (!target) return; target.textContent = text; if (typeof percent === 'number') target.style.color = this.getColorForValue(percent); else target.style.color = ''; }, hideLegacyTabMenu: function() { window.requestAnimationFrame(function() { var menus = document.querySelectorAll('.main > .tabmenu, .main > .cbi-tabmenu'); menus.forEach(function(menu) { menu.style.display = 'none'; }); }); }, getLatestSnapshot: function() { return { cpu: this.cpuHistory[this.cpuHistory.length - 1] || { value: 0 }, memory: this.memoryHistory[this.memoryHistory.length - 1] || { value: 0 }, disk: this.diskHistory[this.diskHistory.length - 1] || { value: 0 }, uptime: (this.latestHealth && this.latestHealth.uptime) || 0, timestamp: (this.latestHealth && this.latestHealth.timestamp) || Date.now() }; }, getNetworkRateSummary: function() { if (this.networkHistory.length < 2) return { summary: '0 B/s' }; var last = this.networkHistory[this.networkHistory.length - 1]; var prev = this.networkHistory[this.networkHistory.length - 2]; var seconds = Math.max(1, (last.time - prev.time) / 1000); var rx = Math.max(0, (last.rx - prev.rx) / seconds); var tx = Math.max(0, (last.tx - prev.tx) / seconds); return { summary: API.formatBytes(rx) + '/s โ†“ ยท ' + API.formatBytes(tx) + '/s โ†‘' }; }, getColorForValue: function(value) { if (value >= 90) return '#ef4444'; if (value >= 75) return '#f59e0b'; if (value >= 50) return '#3b82f6'; return '#22c55e'; }, handleSaveApply: null, handleSave: null, handleReset: null });