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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-16 08:35:03 +01:00
parent b9f789fdb7
commit f424ec72c1
3 changed files with 36 additions and 10 deletions

View File

@ -58,6 +58,26 @@ return view.extend({
if (Array.isArray(data.alerts) && data.alerts.length > 0) { if (Array.isArray(data.alerts) && data.alerts.length > 0) {
alerts = data.alerts; 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 : []; return Array.isArray(alerts) ? alerts : [];
}, },
@ -87,8 +107,8 @@ return view.extend({
// Two column layout // Two column layout
E('div', { 'class': 'kiss-grid kiss-grid-2' }, [ E('div', { 'class': 'kiss-grid kiss-grid-2' }, [
// Alerts card // Active bans card
KissTheme.card('Recent Alerts', E('div', { 'id': 'cs-alerts' }, this.renderAlerts(s.alerts))), KissTheme.card('Active Bans', E('div', { 'id': 'cs-alerts' }, this.renderAlerts(s.alerts))),
// Health card // Health card
KissTheme.card('System Health', this.renderHealth(s)) KissTheme.card('System Health', this.renderHealth(s))
]), ]),
@ -136,19 +156,24 @@ return view.extend({
renderAlerts: function(alerts) { renderAlerts: function(alerts) {
alerts = Array.isArray(alerts) ? alerts : []; alerts = Array.isArray(alerts) ? alerts : [];
if (!alerts.length) { 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' }, [ return E('table', { 'class': 'kiss-table' }, [
E('thead', {}, E('tr', {}, [ E('thead', {}, E('tr', {}, [
E('th', {}, 'Time'), E('th', {}, hasDuration ? 'Expires' : 'Time'),
E('th', {}, 'Source'), E('th', {}, 'Source'),
E('th', {}, 'Scenario') E('th', {}, 'Scenario')
])), ])),
E('tbody', {}, alerts.slice(0, 8).map(function(a) { E('tbody', {}, alerts.slice(0, 8).map(function(a) {
var src = a.source || {}; 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', {}, [ return E('tr', {}, [
E('td', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-muted);' }, api.formatRelativeTime(a.created_at)), 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 || '-')), 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))) E('td', {}, E('span', { 'style': 'font-size: 12px;' }, api.parseScenario(a.scenario)))
]); ]);
})) }))

View File

@ -1,3 +1,3 @@
# CrowdSec Dashboard cache refresh # CrowdSec Dashboard cache refresh
# Refresh overview stats every minute to avoid UI timeouts # 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 * * * * * root ( /usr/libexec/rpcd/luci.crowdsec-dashboard call refresh_cache & ) >/dev/null 2>&1

View File

@ -2612,8 +2612,9 @@ case "$1" in
get_cached_status get_cached_status
;; ;;
refresh_cache) refresh_cache)
refresh_overview_cache # Run in background to avoid blocking rpcd
echo '{"success": true}' ( refresh_overview_cache ) >/dev/null 2>&1 &
echo '{"success": true, "async": true}'
;; ;;
ban) ban)
read -r input read -r input