327 lines
12 KiB
JavaScript
327 lines
12 KiB
JavaScript
'use strict';
|
||
'require view';
|
||
'require poll';
|
||
'require dom';
|
||
'require ui';
|
||
'require netdata-dashboard.api as api';
|
||
|
||
return view.extend({
|
||
title: _('Netdata Dashboard'),
|
||
|
||
// Store history for sparklines
|
||
history: {
|
||
cpu: [],
|
||
memory: [],
|
||
network_rx: [],
|
||
network_tx: []
|
||
},
|
||
maxHistory: 60,
|
||
|
||
load: function() {
|
||
return api.getAllData();
|
||
},
|
||
|
||
addToHistory: function(key, value) {
|
||
this.history[key].push(value);
|
||
if (this.history[key].length > this.maxHistory) {
|
||
this.history[key].shift();
|
||
}
|
||
},
|
||
|
||
renderGauge: function(percent, label, size) {
|
||
size = size || 140;
|
||
var radius = (size - 20) / 2;
|
||
var circumference = 2 * Math.PI * radius;
|
||
var offset = circumference - (percent / 100 * circumference);
|
||
var statusClass = api.getStatusClass(percent);
|
||
|
||
return E('div', { 'class': 'nd-gauge', 'style': 'width:' + size + 'px;height:' + size + 'px' }, [
|
||
E('svg', { 'class': 'nd-gauge-svg', 'width': size, 'height': size, 'viewBox': '0 0 ' + size + ' ' + size }, [
|
||
E('circle', {
|
||
'class': 'nd-gauge-bg',
|
||
'cx': size/2, 'cy': size/2, 'r': radius
|
||
}),
|
||
E('circle', {
|
||
'class': 'nd-gauge-fill ' + statusClass,
|
||
'cx': size/2, 'cy': size/2, 'r': radius,
|
||
'stroke-dasharray': circumference,
|
||
'stroke-dashoffset': offset
|
||
})
|
||
]),
|
||
E('div', { 'class': 'nd-gauge-text' }, [
|
||
E('div', { 'class': 'nd-gauge-value ' + statusClass }, percent + '%'),
|
||
E('div', { 'class': 'nd-gauge-label' }, label)
|
||
])
|
||
]);
|
||
},
|
||
|
||
renderSparkline: function(data, color, height) {
|
||
height = height || 60;
|
||
var width = 200;
|
||
|
||
if (!data || data.length < 2) {
|
||
return E('div', { 'class': 'nd-sparkline', 'style': 'height:' + height + 'px' });
|
||
}
|
||
|
||
var max = Math.max.apply(null, data) || 1;
|
||
var min = Math.min.apply(null, data);
|
||
var range = max - min || 1;
|
||
|
||
var points = data.map(function(v, i) {
|
||
var x = (i / (data.length - 1)) * width;
|
||
var y = height - ((v - min) / range) * (height - 10) - 5;
|
||
return x + ',' + y;
|
||
});
|
||
|
||
var areaPoints = '0,' + height + ' ' + points.join(' ') + ' ' + width + ',' + height;
|
||
|
||
return E('svg', { 'class': 'nd-sparkline', 'width': width, 'height': height, 'viewBox': '0 0 ' + width + ' ' + height }, [
|
||
E('defs', {}, [
|
||
E('linearGradient', { 'id': 'sparkGrad-' + color, 'x1': '0%', 'y1': '0%', 'x2': '0%', 'y2': '100%' }, [
|
||
E('stop', { 'offset': '0%', 'style': 'stop-color:' + color + ';stop-opacity:0.3' }),
|
||
E('stop', { 'offset': '100%', 'style': 'stop-color:' + color + ';stop-opacity:0' })
|
||
])
|
||
]),
|
||
E('polygon', { 'class': 'nd-sparkline-area', 'points': areaPoints, 'fill': 'url(#sparkGrad-' + color + ')' }),
|
||
E('polyline', { 'class': 'nd-sparkline-line', 'points': points.join(' '), 'stroke': color, 'fill': 'none', 'stroke-width': '2' }),
|
||
E('circle', { 'class': 'nd-sparkline-dot', 'cx': width, 'cy': points[points.length-1].split(',')[1], 'r': '3', 'fill': color })
|
||
]);
|
||
},
|
||
|
||
render: function(data) {
|
||
var self = this;
|
||
var stats = data.stats || {};
|
||
var system = data.system || {};
|
||
var memory = data.memory || {};
|
||
var network = data.network || {};
|
||
var disk = data.disk || {};
|
||
var sensors = data.sensors || {};
|
||
|
||
// Add to history
|
||
this.addToHistory('cpu', stats.cpu_percent || 0);
|
||
this.addToHistory('memory', stats.memory_percent || 0);
|
||
this.addToHistory('network_rx', stats.network_rx || 0);
|
||
this.addToHistory('network_tx', stats.network_tx || 0);
|
||
|
||
var cpuClass = api.getStatusClass(stats.cpu_percent || 0);
|
||
var memClass = api.getStatusClass(stats.memory_percent || 0);
|
||
var diskClass = api.getStatusClass(stats.disk_percent || 0);
|
||
var temp = stats.temperature || 0;
|
||
var tempClass = api.getTempClass(temp);
|
||
|
||
var view = E('div', { 'class': 'netdata-dashboard' }, [
|
||
// Header
|
||
E('div', { 'class': 'nd-header' }, [
|
||
E('div', { 'class': 'nd-logo' }, [
|
||
E('div', { 'class': 'nd-logo-icon' }, '📊'),
|
||
E('div', { 'class': 'nd-logo-text' }, ['Net', E('span', {}, 'data')])
|
||
]),
|
||
E('div', { 'class': 'nd-header-info' }, [
|
||
E('div', { 'class': 'nd-hostname' }, system.hostname || 'OpenWrt'),
|
||
E('div', { 'class': 'nd-live-badge' }, [
|
||
E('span', { 'class': 'nd-live-dot' }),
|
||
'Live'
|
||
])
|
||
])
|
||
]),
|
||
|
||
// Quick Stats
|
||
E('div', { 'class': 'nd-quick-stats' }, [
|
||
E('div', { 'class': 'nd-quick-stat' }, [
|
||
E('div', { 'class': 'nd-quick-stat-value ' + cpuClass }, (stats.cpu_percent || 0) + '%'),
|
||
E('div', { 'class': 'nd-quick-stat-label' }, 'CPU')
|
||
]),
|
||
E('div', { 'class': 'nd-quick-stat' }, [
|
||
E('div', { 'class': 'nd-quick-stat-value ' + memClass }, (stats.memory_percent || 0) + '%'),
|
||
E('div', { 'class': 'nd-quick-stat-label' }, 'Memory')
|
||
]),
|
||
E('div', { 'class': 'nd-quick-stat' }, [
|
||
E('div', { 'class': 'nd-quick-stat-value ' + diskClass }, (stats.disk_percent || 0) + '%'),
|
||
E('div', { 'class': 'nd-quick-stat-label' }, 'Disk')
|
||
]),
|
||
E('div', { 'class': 'nd-quick-stat' }, [
|
||
E('div', { 'class': 'nd-quick-stat-value info' }, stats.load || '0.00'),
|
||
E('div', { 'class': 'nd-quick-stat-label' }, 'Load')
|
||
]),
|
||
E('div', { 'class': 'nd-quick-stat' }, [
|
||
E('div', { 'class': 'nd-quick-stat-value ' + tempClass }, temp + '°C'),
|
||
E('div', { 'class': 'nd-quick-stat-label' }, 'Temp')
|
||
]),
|
||
E('div', { 'class': 'nd-quick-stat' }, [
|
||
E('div', { 'class': 'nd-quick-stat-value info' }, stats.processes || 0),
|
||
E('div', { 'class': 'nd-quick-stat-label' }, 'Processes')
|
||
]),
|
||
E('div', { 'class': 'nd-quick-stat' }, [
|
||
E('div', { 'class': 'nd-quick-stat-value info' }, stats.connections || 0),
|
||
E('div', { 'class': 'nd-quick-stat-label' }, 'Connections')
|
||
]),
|
||
E('div', { 'class': 'nd-quick-stat' }, [
|
||
E('div', { 'class': 'nd-quick-stat-value good' }, api.formatUptime(stats.uptime || 0)),
|
||
E('div', { 'class': 'nd-quick-stat-label' }, 'Uptime')
|
||
])
|
||
]),
|
||
|
||
// Charts Grid
|
||
E('div', { 'class': 'nd-charts-grid' }, [
|
||
// CPU Gauge
|
||
E('div', { 'class': 'nd-chart-card' }, [
|
||
E('div', { 'class': 'nd-chart-header' }, [
|
||
E('div', { 'class': 'nd-chart-title' }, [
|
||
E('span', { 'class': 'nd-chart-title-icon' }, '⚡'),
|
||
'CPU Usage'
|
||
]),
|
||
E('div', { 'class': 'nd-chart-value' }, (stats.cpu_percent || 0) + '%')
|
||
]),
|
||
E('div', { 'class': 'nd-chart-body', 'style': 'display:flex;align-items:center;justify-content:space-around' }, [
|
||
this.renderGauge(stats.cpu_percent || 0, 'CPU', 140),
|
||
E('div', { 'style': 'text-align:center' }, [
|
||
this.renderSparkline(this.history.cpu, '#3fb950', 60),
|
||
E('div', { 'style': 'margin-top:8px;font-size:11px;color:#8b949e' }, 'Last 60 samples')
|
||
])
|
||
])
|
||
]),
|
||
|
||
// Memory
|
||
E('div', { 'class': 'nd-chart-card' }, [
|
||
E('div', { 'class': 'nd-chart-header' }, [
|
||
E('div', { 'class': 'nd-chart-title' }, [
|
||
E('span', { 'class': 'nd-chart-title-icon' }, '🧠'),
|
||
'Memory Usage'
|
||
]),
|
||
E('div', { 'class': 'nd-chart-value' }, api.formatKB(memory.used || 0) + ' / ' + api.formatKB(memory.total || 0))
|
||
]),
|
||
E('div', { 'class': 'nd-chart-body' }, [
|
||
E('div', { 'class': 'nd-stacked-bar' }, [
|
||
E('div', { 'class': 'nd-stacked-segment', 'style': 'width:' + (memory.pct_used || 0) + '%;background:#f85149' }),
|
||
E('div', { 'class': 'nd-stacked-segment', 'style': 'width:' + (memory.pct_buffers || 0) + '%;background:#d29922' }),
|
||
E('div', { 'class': 'nd-stacked-segment', 'style': 'width:' + (memory.pct_cached || 0) + '%;background:#3fb950' })
|
||
]),
|
||
E('div', { 'class': 'nd-stacked-legend' }, [
|
||
E('div', { 'class': 'nd-legend-item' }, [
|
||
E('span', { 'class': 'nd-legend-dot', 'style': 'background:#f85149' }),
|
||
'Used: ' + api.formatKB(memory.used || 0)
|
||
]),
|
||
E('div', { 'class': 'nd-legend-item' }, [
|
||
E('span', { 'class': 'nd-legend-dot', 'style': 'background:#d29922' }),
|
||
'Buffers: ' + api.formatKB(memory.buffers || 0)
|
||
]),
|
||
E('div', { 'class': 'nd-legend-item' }, [
|
||
E('span', { 'class': 'nd-legend-dot', 'style': 'background:#3fb950' }),
|
||
'Cached: ' + api.formatKB(memory.cached || 0)
|
||
]),
|
||
E('div', { 'class': 'nd-legend-item' }, [
|
||
E('span', { 'class': 'nd-legend-dot', 'style': 'background:#21262d' }),
|
||
'Free: ' + api.formatKB(memory.free || 0)
|
||
])
|
||
]),
|
||
E('div', { 'style': 'margin-top:16px;text-align:center' },
|
||
this.renderSparkline(this.history.memory, '#58a6ff', 50)
|
||
)
|
||
])
|
||
]),
|
||
|
||
// Network
|
||
E('div', { 'class': 'nd-chart-card' }, [
|
||
E('div', { 'class': 'nd-chart-header' }, [
|
||
E('div', { 'class': 'nd-chart-title' }, [
|
||
E('span', { 'class': 'nd-chart-title-icon' }, '🌐'),
|
||
'Network Traffic'
|
||
])
|
||
]),
|
||
E('div', { 'class': 'nd-chart-body' }, [
|
||
E('div', { 'class': 'nd-network-stats' }, [
|
||
E('div', { 'class': 'nd-network-direction' }, [
|
||
E('div', { 'class': 'nd-network-icon' }, '📥'),
|
||
E('div', { 'class': 'nd-network-value rx' }, api.formatBytes(stats.network_rx || 0)),
|
||
E('div', { 'class': 'nd-network-label' }, 'Received')
|
||
]),
|
||
E('div', { 'class': 'nd-network-direction' }, [
|
||
E('div', { 'class': 'nd-network-icon' }, '📤'),
|
||
E('div', { 'class': 'nd-network-value tx' }, api.formatBytes(stats.network_tx || 0)),
|
||
E('div', { 'class': 'nd-network-label' }, 'Transmitted')
|
||
])
|
||
]),
|
||
E('div', { 'style': 'margin-top:16px;display:flex;gap:20px;justify-content:center' }, [
|
||
this.renderSparkline(this.history.network_rx, '#3fb950', 40),
|
||
this.renderSparkline(this.history.network_tx, '#58a6ff', 40)
|
||
])
|
||
])
|
||
]),
|
||
|
||
// Disk
|
||
E('div', { 'class': 'nd-chart-card' }, [
|
||
E('div', { 'class': 'nd-chart-header' }, [
|
||
E('div', { 'class': 'nd-chart-title' }, [
|
||
E('span', { 'class': 'nd-chart-title-icon' }, '💾'),
|
||
'Disk Usage'
|
||
])
|
||
]),
|
||
E('div', { 'class': 'nd-chart-body' },
|
||
(disk.filesystems || []).slice(0, 4).map(function(fs) {
|
||
var pct = fs.pct_used || 0;
|
||
var diskStatus = pct >= 90 ? 'danger' : (pct >= 70 ? 'warning' : '');
|
||
return E('div', { 'class': 'nd-disk-item' }, [
|
||
E('div', { 'class': 'nd-disk-header' }, [
|
||
E('span', { 'class': 'nd-disk-mount' }, fs.mount),
|
||
E('span', { 'class': 'nd-disk-size' }, api.formatKB(fs.used) + ' / ' + api.formatKB(fs.size))
|
||
]),
|
||
E('div', { 'class': 'nd-disk-bar' }, [
|
||
E('div', { 'class': 'nd-disk-fill ' + diskStatus, 'style': 'width:' + pct + '%' })
|
||
])
|
||
]);
|
||
})
|
||
)
|
||
])
|
||
]),
|
||
|
||
// System Info
|
||
E('div', { 'class': 'nd-chart-card', 'style': 'margin-top:16px' }, [
|
||
E('div', { 'class': 'nd-chart-header' }, [
|
||
E('div', { 'class': 'nd-chart-title' }, [
|
||
E('span', { 'class': 'nd-chart-title-icon' }, '🖥️'),
|
||
'System Information'
|
||
])
|
||
]),
|
||
E('div', { 'class': 'nd-chart-body' }, [
|
||
E('div', { 'class': 'nd-info-grid' }, [
|
||
E('div', { 'class': 'nd-info-item' }, [
|
||
E('div', { 'class': 'nd-info-label' }, 'Hostname'),
|
||
E('div', { 'class': 'nd-info-value' }, system.hostname || 'N/A')
|
||
]),
|
||
E('div', { 'class': 'nd-info-item' }, [
|
||
E('div', { 'class': 'nd-info-label' }, 'Model'),
|
||
E('div', { 'class': 'nd-info-value' }, system.model || 'N/A')
|
||
]),
|
||
E('div', { 'class': 'nd-info-item' }, [
|
||
E('div', { 'class': 'nd-info-label' }, 'Kernel'),
|
||
E('div', { 'class': 'nd-info-value' }, system.kernel || 'N/A')
|
||
]),
|
||
E('div', { 'class': 'nd-info-item' }, [
|
||
E('div', { 'class': 'nd-info-label' }, 'Architecture'),
|
||
E('div', { 'class': 'nd-info-value' }, system.arch || 'N/A')
|
||
]),
|
||
E('div', { 'class': 'nd-info-item' }, [
|
||
E('div', { 'class': 'nd-info-label' }, 'OpenWrt Version'),
|
||
E('div', { 'class': 'nd-info-value' }, system.version || 'N/A')
|
||
]),
|
||
E('div', { 'class': 'nd-info-item' }, [
|
||
E('div', { 'class': 'nd-info-label' }, 'Uptime'),
|
||
E('div', { 'class': 'nd-info-value' }, system.uptime_formatted || 'N/A')
|
||
])
|
||
])
|
||
])
|
||
])
|
||
]);
|
||
|
||
// Include CSS
|
||
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('netdata-dashboard/dashboard.css') });
|
||
document.head.appendChild(cssLink);
|
||
|
||
return view;
|
||
},
|
||
|
||
handleSaveApply: null,
|
||
handleSave: null,
|
||
handleReset: null
|
||
});
|