CrowdSec Widget#
The CrowdSec widget connects to a CrowdSec Local API (LAPI) and displays two metrics: alerts (events raised by the local engine in the last 24 hours) and bans (active decisions of type ban originating from the local CrowdSec engine).
Key files:
| File | Purpose |
|---|---|
src/widgets/crowdsec/widget.js | API endpoint definitions and query parameters |
src/widgets/crowdsec/component.jsx | React component — data fetching, null-safety, truncation display |
src/components/services/widget/block.jsx | Generic Block component — controls loading animation |
API Query Parameters#
Both endpoints hit /v1/alerts on the CrowdSec LAPI. Their current query strings are:
alerts→alerts?limit=500&since=24h&with_decisions=false&include_capi=falsebans→alerts?decision_type=ban&include_capi=false&has_active_decision=1&with_decisions=false
Why each parameter matters#
| Parameter | Effect |
|---|---|
with_decisions=false | Strips embedded decision objects from each alert record, dramatically reducing payload size. Without this, responses can exceed 4 MB and trigger Next.js API route warnings. |
include_capi=false | Excludes community blocklist (CAPI) alerts. Without it, the bans query could time out (HTTP 504) on large community blocklists. |
since=24h | Restricts alerts to the last 24 hours. Without a time bound, the unlimited query returns weeks of historical data. |
limit=500 | Hard caps alert results. When 500 records are returned, the component displays 500+ to signal truncation. |
has_active_decision=1 | Filters the bans query to only currently-active decisions — without it, expired bans are included in the count. |
Historical note: Earlier versions used
limit=0(no limit) and nosincebound, producing unbounded payloads. The current shape was introduced in PR #7075 merged 2026-08-30.
Known Issue: 0 Bans Despite Active Decisions#
The bans endpoint queries alerts, not /v1/decisions. It filters for decision_type=ban&origin=crowdsec&has_active_decision=1, which means:
- Only bans that were created by the local engine (
origin=crowdsec) are counted. - CAPI bans are excluded (
include_capi=false). - The count reflects alerts, not the raw decisions list — so it can differ from
cscli decisions list.
If bans show 0 despite active decisions, verify that those decisions have origin=crowdsec (not capi). Decisions imported from the community blocklist will not appear.
Stuck Loading Animation / Incorrect Zero Display#
The Block component triggers a CSS animate-pulse skeleton when value === undefined — i.e., while data is still loading.
The component renders without a value (loading state) when both alerts and bans are explicitly undefined:
if (alerts === undefined && bans === undefined) {
return <Container ...><Block .../><Block .../></Container>;
}
Prior implementation: The check used a falsy comparison (if (!alerts && !bans)), which treated empty arrays ([]) the same as undefined. This caused the loading animation to persist when the CrowdSec API returned a literal null string response (which was parsed and passed through as-is).
Fix (PR #7088): The proxy now normalizes literal null responses from the CrowdSec API to empty arrays ([]). The component then uses explicit undefined checks (=== undefined) to distinguish between "loading" (data not yet fetched) and "no data" (empty array or normalized null). This ensures the widget displays 0 counts correctly and only shows the loading animation while data is in-flight.
Separately, the bans count uses optional chaining with a nullish fallback: bans?.length ?? 0. This means a null or undefined bans response silently displays 0 once the alerts data has arrived — which can mask a failed or timed-out bans request.
Configuration#
widget:
type: crowdsec
url: http://crowdsechostorip:port
username: localhost # machine_id from /etc/crowdsec/local_api_credentials.yaml
password: password
fields: ["alerts", "bans"]
The deprecated limit24h option (previously toggled between alerts and alerts24h endpoints) is ignored as of PR #7075; the widget now always uses a 24-hour window.