From f424ec72c1d92ec5fbc80e367f2ca9700e2d1da2 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Mon, 16 Mar 2026 08:35:03 +0100 Subject: [PATCH] fix(crowdsec): Fix rpcd blocking and show active bans - Make refresh_cache async to prevent rpcd watchdog kills - Fix JSON escaping for top_scenarios/countries arrays - Show decisions as "Active Bans" when alerts_raw is empty - Display ban expiry time instead of creation time - Update cron to run cache refresh in background Fixes LuCI crashes caused by 16s blocking refresh calls. Co-Authored-By: Claude Opus 4.5 --- .../view/crowdsec-dashboard/overview.js | 37 ++++++++++++++++--- .../root/etc/cron.d/crowdsec-dashboard | 4 +- .../usr/libexec/rpcd/luci.crowdsec-dashboard | 5 ++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/overview.js b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/overview.js index 5887b0e5..95fb472d 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/overview.js +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/overview.js @@ -58,6 +58,26 @@ return view.extend({ if (Array.isArray(data.alerts) && data.alerts.length > 0) { alerts = data.alerts; } + // Fallback to decisions_raw if no alerts (decisions = active bans from alerts) + if ((!alerts || alerts.length === 0) && data.decisions_raw) { + try { + var decisions = typeof data.decisions_raw === 'string' + ? JSON.parse(data.decisions_raw) + : data.decisions_raw; + if (Array.isArray(decisions)) { + // Convert decisions to alert-like format + alerts = decisions.map(function(d) { + return { + source_ip: d.value, + scenario: d.scenario, + created_at: new Date().toISOString(), // No timestamp in decision, use now + type: d.type, + duration: d.duration + }; + }); + } + } catch (e) {} + } return Array.isArray(alerts) ? alerts : []; }, @@ -87,8 +107,8 @@ return view.extend({ // Two column layout E('div', { 'class': 'kiss-grid kiss-grid-2' }, [ - // Alerts card - KissTheme.card('Recent Alerts', E('div', { 'id': 'cs-alerts' }, this.renderAlerts(s.alerts))), + // Active bans card + KissTheme.card('Active Bans', E('div', { 'id': 'cs-alerts' }, this.renderAlerts(s.alerts))), // Health card KissTheme.card('System Health', this.renderHealth(s)) ]), @@ -136,19 +156,24 @@ return view.extend({ renderAlerts: function(alerts) { alerts = Array.isArray(alerts) ? alerts : []; if (!alerts.length) { - return E('div', { 'style': 'text-align: center; padding: 24px; color: var(--kiss-muted);' }, 'No recent alerts'); + return E('div', { 'style': 'text-align: center; padding: 24px; color: var(--kiss-muted);' }, 'No active bans'); } + // Check if we have duration (from decisions) or created_at (from alerts) + var hasDuration = alerts[0] && alerts[0].duration; return E('table', { 'class': 'kiss-table' }, [ E('thead', {}, E('tr', {}, [ - E('th', {}, 'Time'), + E('th', {}, hasDuration ? 'Expires' : 'Time'), E('th', {}, 'Source'), E('th', {}, 'Scenario') ])), E('tbody', {}, alerts.slice(0, 8).map(function(a) { var src = a.source || {}; + var timeCol = a.duration + ? a.duration.replace(/([0-9]+)h([0-9]+)m.*/, '$1h $2m') + : api.formatRelativeTime(a.created_at); return E('tr', {}, [ - E('td', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-muted);' }, api.formatRelativeTime(a.created_at)), - E('td', {}, E('span', { 'style': 'font-family: monospace; color: var(--kiss-cyan);' }, src.ip || a.source_ip || '-')), + E('td', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-muted);' }, timeCol), + E('td', {}, E('span', { 'style': 'font-family: monospace; color: var(--kiss-cyan);' }, src.ip || a.source_ip || a.value || '-')), E('td', {}, E('span', { 'style': 'font-size: 12px;' }, api.parseScenario(a.scenario))) ]); })) diff --git a/package/secubox/luci-app-crowdsec-dashboard/root/etc/cron.d/crowdsec-dashboard b/package/secubox/luci-app-crowdsec-dashboard/root/etc/cron.d/crowdsec-dashboard index 3178ea32..50c5dfcb 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/root/etc/cron.d/crowdsec-dashboard +++ b/package/secubox/luci-app-crowdsec-dashboard/root/etc/cron.d/crowdsec-dashboard @@ -1,3 +1,3 @@ # CrowdSec Dashboard cache refresh -# Refresh overview stats every minute to avoid UI timeouts -* * * * * root /usr/libexec/rpcd/luci.crowdsec-dashboard call refresh_cache >/dev/null 2>&1 +# Refresh overview stats every minute - runs in background to avoid blocking rpcd +* * * * * root ( /usr/libexec/rpcd/luci.crowdsec-dashboard call refresh_cache & ) >/dev/null 2>&1 diff --git a/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard b/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard index 9a59caf2..1f90eda4 100755 --- a/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard +++ b/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard @@ -2612,8 +2612,9 @@ case "$1" in get_cached_status ;; refresh_cache) - refresh_overview_cache - echo '{"success": true}' + # Run in background to avoid blocking rpcd + ( refresh_overview_cache ) >/dev/null 2>&1 & + echo '{"success": true, "async": true}' ;; ban) read -r input