fix: Netdata Dashboard improvements and bug fixes

- Improve dashboard rendering and service status display
- Fix settings UI layout and validation
- Update RPCD backend for better error handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-09 09:32:36 +01:00
parent 9ef0b6db18
commit e8e177d655
3 changed files with 140 additions and 91 deletions

View File

@ -28,7 +28,9 @@ return view.extend({
var logs = (data[3] && data[3].entries) || []; var logs = (data[3] && data[3].entries) || [];
var isRunning = netdataStatus.running || false; var isRunning = netdataStatus.running || false;
var netdataUrl = netdataStatus.url || 'http://127.0.0.1:19999'; var netdataPort = netdataStatus.port || 19999;
// Use current browser hostname for iframe (not 127.0.0.1 which won't work)
var netdataUrl = 'http://' + window.location.hostname + ':' + netdataPort;
var alarmCount = this.countAlarms(alarms); var alarmCount = this.countAlarms(alarms);
var view = E('div', { 'class': 'netdata-dashboard secubox-netdata' }, [ var view = E('div', { 'class': 'netdata-dashboard secubox-netdata' }, [
@ -69,28 +71,45 @@ return view.extend({
}, },
renderHeader: function(status, stats) { renderHeader: function(status, stats) {
return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ var headerStyle = 'margin-bottom: 1.5rem;';
var titleStyle = 'display: flex; align-items: center; gap: 0.5rem; margin: 0 0 0.5rem 0; font-size: 1.5rem;';
var subtitleStyle = 'margin: 0; color: #8b949e; font-size: 0.9rem;';
var metaStyle = 'display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 1rem;';
return E('div', { 'style': headerStyle }, [
E('div', {}, [ E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [ E('h2', { 'style': titleStyle }, [
E('span', { 'class': 'sh-page-title-icon' }, '📊'), E('span', {}, '📊'),
_('Netdata Monitoring') _('Netdata Monitoring')
]), ]),
E('p', { 'class': 'sh-page-subtitle' }, E('p', { 'style': subtitleStyle },
_('Real-time analytics for CPU, memory, disk, and services.')) _('Real-time analytics for CPU, memory, disk, and services.'))
]), ]),
E('div', { 'class': 'sh-header-meta' }, [ E('div', { 'style': metaStyle }, [
this.renderHeaderChip(_('Status'), status.running ? _('Online') : _('Offline'), this.renderHeaderChip(_('Status'), status.running ? _('Online') : _('Offline'),
status.running ? 'success' : 'warn'), status.running ? 'success' : 'warn'),
this.renderHeaderChip(_('Version'), status.version || _('Unknown')), this.renderHeaderChip(_('Version'), status.version || _('unknown')),
this.renderHeaderChip(_('Uptime'), API.formatUptime(stats.uptime || 0)) this.renderHeaderChip(_('Uptime'), API.formatUptime(stats.uptime || 0))
]) ])
]); ]);
}, },
renderHeaderChip: function(label, value, tone) { renderHeaderChip: function(label, value, tone) {
return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [ var chipStyle = 'display: flex; flex-direction: column; padding: 0.5rem 1rem; background: #161b22; border: 1px solid #30363d; border-radius: 8px; min-width: 100px;';
E('span', { 'class': 'sh-chip-label' }, label), var labelStyle = 'font-size: 0.75rem; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.25rem;';
E('strong', {}, value) var valueStyle = 'font-size: 1rem; font-weight: 600;';
if (tone === 'success') {
valueStyle += ' color: #3fb950;';
} else if (tone === 'warn') {
valueStyle += ' color: #d29922;';
} else {
valueStyle += ' color: #f0f6fc;';
}
return E('div', { 'style': chipStyle }, [
E('span', { 'style': labelStyle }, label),
E('strong', { 'style': valueStyle }, value)
]); ]);
}, },
@ -115,20 +134,34 @@ return view.extend({
}, },
renderQuickStats: function(stats) { renderQuickStats: function(stats) {
return E('div', { 'class': 'nd-quick-stats' }, [ var gridStyle = 'display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 0.75rem; margin-bottom: 1.5rem;';
this.renderStatCard(_('CPU'), (stats.cpu_percent || 0) + '%'),
this.renderStatCard(_('Memory'), (stats.memory_percent || 0) + '%'), return E('div', { 'style': gridStyle }, [
this.renderStatCard(_('Disk'), (stats.disk_percent || 0) + '%'), this.renderStatCard(_('CPU'), (stats.cpu_percent || 0) + '%', stats.cpu_percent > 80 ? 'danger' : stats.cpu_percent > 50 ? 'warning' : 'good'),
this.renderStatCard(_('Load'), stats.load || '0.00'), this.renderStatCard(_('Memory'), (stats.memory_percent || 0) + '%', stats.memory_percent > 80 ? 'danger' : stats.memory_percent > 50 ? 'warning' : 'good'),
this.renderStatCard(_('Temp'), (stats.temperature || 0) + '°C'), this.renderStatCard(_('Disk'), (stats.disk_percent || 0) + '%', stats.disk_percent > 80 ? 'danger' : stats.disk_percent > 50 ? 'warning' : 'info'),
this.renderStatCard(_('Clients'), stats.clients || 0) this.renderStatCard(_('Load'), stats.load || '0.00', 'info'),
this.renderStatCard(_('Temp'), (stats.temperature || 0) + '°C', stats.temperature > 70 ? 'danger' : stats.temperature > 50 ? 'warning' : 'good'),
this.renderStatCard(_('Clients'), stats.clients || 0, 'info')
]); ]);
}, },
renderStatCard: function(label, value) { renderStatCard: function(label, value, tone) {
return E('div', { 'class': 'nd-stat-card' }, [ var cardStyle = 'display: flex; flex-direction: column; align-items: center; padding: 1rem; background: #161b22; border: 1px solid #30363d; border-radius: 8px; text-align: center;';
E('span', { 'class': 'nd-stat-label' }, label), var labelStyle = 'font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.5px; color: #6e7681; margin-bottom: 0.5rem;';
E('strong', { 'class': 'nd-stat-value' }, value) var valueStyle = 'font-size: 1.5rem; font-weight: 700; font-family: monospace;';
var colors = {
'good': '#3fb950',
'warning': '#d29922',
'danger': '#f85149',
'info': '#58a6ff'
};
valueStyle += ' color: ' + (colors[tone] || '#f0f6fc') + ';';
return E('div', { 'style': cardStyle }, [
E('span', { 'style': labelStyle }, label),
E('strong', { 'style': valueStyle }, String(value))
]); ]);
}, },

View File

@ -18,6 +18,15 @@ return view.extend({
var info = data[1] || {}; var info = data[1] || {};
var system = data[2] || {}; var system = data[2] || {};
// Build URL using browser hostname (not 127.0.0.1 which won't work from browser)
var port = status.port || 19999;
var bind = status.bind || '0.0.0.0';
var dashboardUrl = 'http://' + window.location.hostname + ':' + port;
var tableStyle = 'width: 100%; border-collapse: collapse;';
var thStyle = 'padding: 0.75rem 1rem; text-align: left; font-weight: 600; width: 200px; background: #161b22; border-bottom: 1px solid #30363d;';
var tdStyle = 'padding: 0.75rem 1rem; border-bottom: 1px solid #30363d;';
var view = E('div', { 'class': 'cbi-map' }, [ var view = E('div', { 'class': 'cbi-map' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('h2', {}, _('Netdata Settings')), E('h2', {}, _('Netdata Settings')),
@ -27,35 +36,37 @@ return view.extend({
// Service Information // Service Information
E('div', { 'class': 'cbi-section' }, [ E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Service Information')), E('h3', {}, _('Service Information')),
E('div', { 'class': 'table cbi-section-table' }, [ E('table', { 'style': tableStyle }, [
E('div', { 'class': 'tr cbi-section-table-row' }, [ E('tbody', {}, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Service Status')), E('tr', {}, [
E('div', { 'class': 'td left' }, [ E('th', { 'style': thStyle }, _('Service Status')),
E('span', { E('td', { 'style': tdStyle }, [
'class': 'badge', E('span', {
'style': 'background: ' + (status.running ? '#28a745' : '#dc3545') + '; color: white; padding: 0.25em 0.6em; border-radius: 3px;' 'style': 'display: inline-block; padding: 0.25rem 0.75rem; border-radius: 4px; font-weight: 500; background: ' + (status.running ? '#238636' : '#da3633') + '; color: white;'
}, status.running ? _('Running') : _('Stopped')) }, status.running ? _('Running') : _('Stopped'))
]) ])
]), ]),
E('div', { 'class': 'tr cbi-section-table-row' }, [ E('tr', {}, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Version')), E('th', { 'style': thStyle }, _('Version')),
E('div', { 'class': 'td left' }, status.version || 'Unknown') E('td', { 'style': tdStyle }, status.version || 'unknown')
]), ]),
E('div', { 'class': 'tr cbi-section-table-row' }, [ E('tr', {}, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Listen Port')), E('th', { 'style': thStyle }, _('Listen Port')),
E('div', { 'class': 'td left' }, (status.port || 19999).toString()) E('td', { 'style': tdStyle }, String(port))
]), ]),
E('div', { 'class': 'tr cbi-section-table-row' }, [ E('tr', {}, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Bind Address')), E('th', { 'style': thStyle }, _('Bind Address')),
E('div', { 'class': 'td left' }, status.bind || '127.0.0.1') E('td', { 'style': tdStyle }, bind)
]), ]),
E('div', { 'class': 'tr cbi-section-table-row' }, [ E('tr', {}, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Dashboard URL')), E('th', { 'style': thStyle }, _('Dashboard URL')),
E('div', { 'class': 'td left' }, [ E('td', { 'style': tdStyle }, [
E('a', { E('a', {
'href': status.url || 'http://127.0.0.1:19999', 'href': dashboardUrl,
'target': '_blank' 'target': '_blank',
}, status.url || 'http://127.0.0.1:19999') 'style': 'color: #58a6ff;'
}, dashboardUrl)
])
]) ])
]) ])
]) ])
@ -64,31 +75,32 @@ return view.extend({
// System Information // System Information
E('div', { 'class': 'cbi-section', 'style': 'margin-top: 2em;' }, [ E('div', { 'class': 'cbi-section', 'style': 'margin-top: 2em;' }, [
E('h3', {}, _('System Information')), E('h3', {}, _('System Information')),
E('div', { 'class': 'table cbi-section-table' }, [ E('table', { 'style': tableStyle }, [
E('div', { 'class': 'tr cbi-section-table-row' }, [ E('tbody', {}, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Hostname')), E('tr', {}, [
E('div', { 'class': 'td left' }, system.hostname || 'Unknown') E('th', { 'style': thStyle }, _('Hostname')),
]), E('td', { 'style': tdStyle }, system.hostname || 'Unknown')
E('div', { 'class': 'tr cbi-section-table-row' }, [ ]),
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Model')), E('tr', {}, [
E('div', { 'class': 'td left' }, system.model || 'Unknown') E('th', { 'style': thStyle }, _('Model')),
]), E('td', { 'style': tdStyle }, system.model || 'Unknown')
E('div', { 'class': 'tr cbi-section-table-row' }, [ ]),
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Kernel')), E('tr', {}, [
E('div', { 'class': 'td left' }, system.kernel || 'Unknown') E('th', { 'style': thStyle }, _('Kernel')),
]), E('td', { 'style': tdStyle }, system.kernel || 'Unknown')
E('div', { 'class': 'tr cbi-section-table-row' }, [ ]),
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Architecture')), E('tr', {}, [
E('div', { 'class': 'td left' }, system.arch || 'Unknown') E('th', { 'style': thStyle }, _('Architecture')),
]), E('td', { 'style': tdStyle }, system.arch || 'Unknown')
E('div', { 'class': 'tr cbi-section-table-row' }, [ ]),
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Distribution')), E('tr', {}, [
E('div', { 'class': 'td left' }, E('th', { 'style': thStyle }, _('Distribution')),
(system.distro || 'OpenWrt') + ' ' + (system.version || '')) E('td', { 'style': tdStyle }, (system.distro || 'OpenWrt') + ' ' + (system.version || ''))
]), ]),
E('div', { 'class': 'tr cbi-section-table-row' }, [ E('tr', {}, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Uptime')), E('th', { 'style': thStyle }, _('Uptime')),
E('div', { 'class': 'td left' }, system.uptime_formatted || '0d 0h 0m') E('td', { 'style': tdStyle }, system.uptime_formatted || '0d 0h 0m')
])
]) ])
]) ])
]), ]),

View File

@ -439,8 +439,8 @@ get_stats() {
get_netdata_status() { get_netdata_status() {
json_init json_init
# Check if netdata is running # Check if netdata is running (use pidof for OpenWrt compatibility)
if pgrep -x netdata >/dev/null 2>&1; then if pidof netdata >/dev/null 2>&1; then
json_add_string "service" "running" json_add_string "service" "running"
json_add_boolean "running" 1 json_add_boolean "running" 1
else else
@ -448,23 +448,27 @@ get_netdata_status() {
json_add_boolean "running" 0 json_add_boolean "running" 0
fi fi
# Get configuration # Get configuration with proper defaults
local port="19999" local port=""
local bind="127.0.0.1" local bind=""
if [ -f /etc/netdata/netdata.conf ]; then if [ -f /etc/netdata/netdata.conf ]; then
port=$(grep "^\s*default port" /etc/netdata/netdata.conf 2>/dev/null | awk '{print $NF}' || echo "19999") port=$(grep "^\s*default port" /etc/netdata/netdata.conf 2>/dev/null | awk '{print $NF}')
bind=$(grep "^\s*bind to" /etc/netdata/netdata.conf 2>/dev/null | awk '{print $NF}' || echo "127.0.0.1") bind=$(grep "^\s*bind to" /etc/netdata/netdata.conf 2>/dev/null | awk '{print $NF}')
fi fi
# Apply defaults if empty
[ -z "$port" ] && port="19999"
[ -z "$bind" ] && bind="127.0.0.1"
json_add_int "port" "$port" json_add_int "port" "$port"
json_add_string "bind" "$bind" json_add_string "bind" "$bind"
json_add_string "url" "http://${bind}:${port}" json_add_string "url" "http://${bind}:${port}"
# Try to get version from Netdata API # Try to get version from Netdata API
local version="" local version=""
if command -v curl >/dev/null; then if command -v curl >/dev/null && pidof netdata >/dev/null 2>&1; then
version=$(curl -s "http://${bind}:${port}/api/v1/info" 2>/dev/null | jsonfilter -e '@.version' 2>/dev/null || echo "") version=$(curl -s --connect-timeout 2 "http://${bind}:${port}/api/v1/info" 2>/dev/null | jsonfilter -e '@.version' 2>/dev/null || echo "")
fi fi
json_add_string "version" "${version:-unknown}" json_add_string "version" "${version:-unknown}"
@ -484,7 +488,7 @@ get_netdata_alarms() {
fi fi
# Fetch alarms from Netdata API # Fetch alarms from Netdata API
if command -v curl >/dev/null && pgrep -x netdata >/dev/null; then if command -v curl >/dev/null && pidof netdata >/dev/null; then
local alarms=$(curl -s "http://${bind}:${port}/api/v1/alarms" 2>/dev/null) local alarms=$(curl -s "http://${bind}:${port}/api/v1/alarms" 2>/dev/null)
if [ -n "$alarms" ] && [ "$alarms" != "null" ]; then if [ -n "$alarms" ] && [ "$alarms" != "null" ]; then
echo "$alarms" echo "$alarms"
@ -509,7 +513,7 @@ get_netdata_info() {
fi fi
# Fetch info from Netdata API # Fetch info from Netdata API
if command -v curl >/dev/null && pgrep -x netdata >/dev/null; then if command -v curl >/dev/null && pidof netdata >/dev/null; then
local info=$(curl -s "http://${bind}:${port}/api/v1/info" 2>/dev/null) local info=$(curl -s "http://${bind}:${port}/api/v1/info" 2>/dev/null)
if [ -n "$info" ] && [ "$info" != "null" ]; then if [ -n "$info" ] && [ "$info" != "null" ]; then
echo "$info" echo "$info"
@ -529,7 +533,7 @@ restart_netdata() {
/etc/init.d/netdata restart >/dev/null 2>&1 /etc/init.d/netdata restart >/dev/null 2>&1
sleep 2 sleep 2
if pgrep -x netdata >/dev/null 2>&1; then if pidof netdata >/dev/null 2>&1; then
json_add_boolean "success" 1 json_add_boolean "success" 1
json_add_string "message" "Netdata restarted successfully" json_add_string "message" "Netdata restarted successfully"
secubox_log "Netdata service restarted" secubox_log "Netdata service restarted"
@ -549,14 +553,14 @@ restart_netdata() {
start_netdata() { start_netdata() {
json_init json_init
if pgrep -x netdata >/dev/null 2>&1; then if pidof netdata >/dev/null 2>&1; then
json_add_boolean "success" 1 json_add_boolean "success" 1
json_add_string "message" "Netdata already running" json_add_string "message" "Netdata already running"
elif [ -x /etc/init.d/netdata ]; then elif [ -x /etc/init.d/netdata ]; then
/etc/init.d/netdata start >/dev/null 2>&1 /etc/init.d/netdata start >/dev/null 2>&1
sleep 2 sleep 2
if pgrep -x netdata >/dev/null 2>&1; then if pidof netdata >/dev/null 2>&1; then
json_add_boolean "success" 1 json_add_boolean "success" 1
json_add_string "message" "Netdata started successfully" json_add_string "message" "Netdata started successfully"
secubox_log "Netdata service started" secubox_log "Netdata service started"
@ -576,14 +580,14 @@ start_netdata() {
stop_netdata() { stop_netdata() {
json_init json_init
if ! pgrep -x netdata >/dev/null 2>&1; then if ! pidof netdata >/dev/null 2>&1; then
json_add_boolean "success" 1 json_add_boolean "success" 1
json_add_string "message" "Netdata already stopped" json_add_string "message" "Netdata already stopped"
elif [ -x /etc/init.d/netdata ]; then elif [ -x /etc/init.d/netdata ]; then
/etc/init.d/netdata stop >/dev/null 2>&1 /etc/init.d/netdata stop >/dev/null 2>&1
sleep 1 sleep 1
if ! pgrep -x netdata >/dev/null 2>&1; then if ! pidof netdata >/dev/null 2>&1; then
json_add_boolean "success" 1 json_add_boolean "success" 1
json_add_string "message" "Netdata stopped successfully" json_add_string "message" "Netdata stopped successfully"
secubox_log "Netdata service stopped" secubox_log "Netdata service stopped"