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:
parent
b9f789fdb7
commit
f424ec72c1
@ -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)))
|
||||||
]);
|
]);
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user