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 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 view = E('div', { 'class': 'netdata-dashboard secubox-netdata' }, [
@ -69,28 +71,45 @@ return view.extend({
},
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('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '📊'),
E('h2', { 'style': titleStyle }, [
E('span', {}, '📊'),
_('Netdata Monitoring')
]),
E('p', { 'class': 'sh-page-subtitle' },
E('p', { 'style': subtitleStyle },
_('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'),
status.running ? 'success' : 'warn'),
this.renderHeaderChip(_('Version'), status.version || _('Unknown')),
this.renderHeaderChip(_('Version'), status.version || _('unknown')),
this.renderHeaderChip(_('Uptime'), API.formatUptime(stats.uptime || 0))
])
]);
},
renderHeaderChip: function(label, value, tone) {
return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, value)
var chipStyle = 'display: flex; flex-direction: column; padding: 0.5rem 1rem; background: #161b22; border: 1px solid #30363d; border-radius: 8px; min-width: 100px;';
var labelStyle = 'font-size: 0.75rem; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.25rem;';
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) {
return E('div', { 'class': 'nd-quick-stats' }, [
this.renderStatCard(_('CPU'), (stats.cpu_percent || 0) + '%'),
this.renderStatCard(_('Memory'), (stats.memory_percent || 0) + '%'),
this.renderStatCard(_('Disk'), (stats.disk_percent || 0) + '%'),
this.renderStatCard(_('Load'), stats.load || '0.00'),
this.renderStatCard(_('Temp'), (stats.temperature || 0) + '°C'),
this.renderStatCard(_('Clients'), stats.clients || 0)
var gridStyle = 'display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 0.75rem; margin-bottom: 1.5rem;';
return E('div', { 'style': gridStyle }, [
this.renderStatCard(_('CPU'), (stats.cpu_percent || 0) + '%', stats.cpu_percent > 80 ? 'danger' : stats.cpu_percent > 50 ? 'warning' : 'good'),
this.renderStatCard(_('Memory'), (stats.memory_percent || 0) + '%', stats.memory_percent > 80 ? 'danger' : stats.memory_percent > 50 ? 'warning' : 'good'),
this.renderStatCard(_('Disk'), (stats.disk_percent || 0) + '%', stats.disk_percent > 80 ? 'danger' : stats.disk_percent > 50 ? 'warning' : 'info'),
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) {
return E('div', { 'class': 'nd-stat-card' }, [
E('span', { 'class': 'nd-stat-label' }, label),
E('strong', { 'class': 'nd-stat-value' }, value)
renderStatCard: function(label, value, tone) {
var cardStyle = 'display: flex; flex-direction: column; align-items: center; padding: 1rem; background: #161b22; border: 1px solid #30363d; border-radius: 8px; text-align: center;';
var labelStyle = 'font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.5px; color: #6e7681; margin-bottom: 0.5rem;';
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 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' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
E('h2', {}, _('Netdata Settings')),
@ -27,35 +36,37 @@ return view.extend({
// Service Information
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Service Information')),
E('div', { 'class': 'table cbi-section-table' }, [
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Service Status')),
E('div', { 'class': 'td left' }, [
E('span', {
'class': 'badge',
'style': 'background: ' + (status.running ? '#28a745' : '#dc3545') + '; color: white; padding: 0.25em 0.6em; border-radius: 3px;'
}, status.running ? _('Running') : _('Stopped'))
])
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Version')),
E('div', { 'class': 'td left' }, status.version || 'Unknown')
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Listen Port')),
E('div', { 'class': 'td left' }, (status.port || 19999).toString())
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Bind Address')),
E('div', { 'class': 'td left' }, status.bind || '127.0.0.1')
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Dashboard URL')),
E('div', { 'class': 'td left' }, [
E('a', {
'href': status.url || 'http://127.0.0.1:19999',
'target': '_blank'
}, status.url || 'http://127.0.0.1:19999')
E('table', { 'style': tableStyle }, [
E('tbody', {}, [
E('tr', {}, [
E('th', { 'style': thStyle }, _('Service Status')),
E('td', { 'style': tdStyle }, [
E('span', {
'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'))
])
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Version')),
E('td', { 'style': tdStyle }, status.version || 'unknown')
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Listen Port')),
E('td', { 'style': tdStyle }, String(port))
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Bind Address')),
E('td', { 'style': tdStyle }, bind)
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Dashboard URL')),
E('td', { 'style': tdStyle }, [
E('a', {
'href': dashboardUrl,
'target': '_blank',
'style': 'color: #58a6ff;'
}, dashboardUrl)
])
])
])
])
@ -64,31 +75,32 @@ return view.extend({
// System Information
E('div', { 'class': 'cbi-section', 'style': 'margin-top: 2em;' }, [
E('h3', {}, _('System Information')),
E('div', { 'class': 'table cbi-section-table' }, [
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Hostname')),
E('div', { 'class': 'td left' }, system.hostname || 'Unknown')
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Model')),
E('div', { 'class': 'td left' }, system.model || 'Unknown')
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Kernel')),
E('div', { 'class': 'td left' }, system.kernel || 'Unknown')
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Architecture')),
E('div', { 'class': 'td left' }, system.arch || 'Unknown')
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Distribution')),
E('div', { 'class': 'td left' },
(system.distro || 'OpenWrt') + ' ' + (system.version || ''))
]),
E('div', { 'class': 'tr cbi-section-table-row' }, [
E('div', { 'class': 'td left', 'style': 'width: 33%; font-weight: bold;' }, _('Uptime')),
E('div', { 'class': 'td left' }, system.uptime_formatted || '0d 0h 0m')
E('table', { 'style': tableStyle }, [
E('tbody', {}, [
E('tr', {}, [
E('th', { 'style': thStyle }, _('Hostname')),
E('td', { 'style': tdStyle }, system.hostname || 'Unknown')
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Model')),
E('td', { 'style': tdStyle }, system.model || 'Unknown')
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Kernel')),
E('td', { 'style': tdStyle }, system.kernel || 'Unknown')
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Architecture')),
E('td', { 'style': tdStyle }, system.arch || 'Unknown')
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Distribution')),
E('td', { 'style': tdStyle }, (system.distro || 'OpenWrt') + ' ' + (system.version || ''))
]),
E('tr', {}, [
E('th', { 'style': thStyle }, _('Uptime')),
E('td', { 'style': tdStyle }, system.uptime_formatted || '0d 0h 0m')
])
])
])
]),

View File

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