secubox-openwrt/package/secubox/luci-app-netdata-dashboard/htdocs/luci-static/resources/view/netdata-dashboard/realtime.js
CyberMind-FR 31a87c5d7a feat(structure): reorganize luci-app packages into package/secubox/ + appstore migration
Major structural reorganization and feature additions:

## Folder Reorganization
- Move 17 luci-app-* packages to package/secubox/ (except luci-app-secubox core hub)
- Update all tooling to support new structure:
  - secubox-tools/quick-deploy.sh: search both locations
  - secubox-tools/validate-modules.sh: validate both directories
  - secubox-tools/fix-permissions.sh: fix permissions in both locations
  - .github/workflows/test-validate.yml: build from both paths
- Update README.md links to new package/secubox/ paths

## AppStore Migration (Complete)
- Add catalog entries for all remaining luci-app packages:
  - network-tweaks.json: Network optimization tools
  - secubox-bonus.json: Documentation & demos hub
- Total: 24 apps in AppStore catalog (22 existing + 2 new)
- New category: 'documentation' for docs/demos/tutorials

## VHost Manager v2.0 Enhancements
- Add profile activation system for Internal Services and Redirects
- Implement createVHost() API wrapper for template-based deployment
- Fix Virtual Hosts view rendering with proper LuCI patterns
- Fix RPCD backend shell script errors (remove invalid local declarations)
- Extend backend validation for nginx return directives (redirect support)
- Add section_id parameter for named VHost profiles
- Add Remove button to Redirects page for feature parity
- Update README to v2.0 with comprehensive feature documentation

## Network Tweaks Dashboard
- Close button added to component details modal

Files changed: 340+ (336 renames with preserved git history)
Packages affected: 19 luci-app, 2 secubox-app, 1 theme, 4 tools

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 14:59:38 +01:00

329 lines
12 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require secubox-theme/theme as Theme';
'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('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
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
});