Fixhancement: crowdsec widget improvements (#7075)
Docker CI / Docker Build & Push (push) Waiting to run
Lint / Linting Checks (push) Waiting to run
Release Drafter / Update Release Draft (push) Waiting to run
Release Drafter / Auto Label PR (push) Waiting to run
Tests / vitest (1) (push) Waiting to run
Tests / vitest (2) (push) Waiting to run
Tests / vitest (3) (push) Waiting to run
Tests / vitest (4) (push) Waiting to run

This commit is contained in:
shamoon
2026-08-29 23:43:19 -07:00
committed by GitHub
parent e6ad43a76c
commit f6dca0c5ad
4 changed files with 49 additions and 12 deletions
+3 -2
View File
@@ -9,7 +9,9 @@ See the [crowdsec docs](https://docs.crowdsec.net/docs/local_api/intro/#machines
in most instances you can use the default credentials (`/etc/crowdsec/local_api_credentials.yaml`).
!!! note
Without the `limit24h` option, the widget will fetch all alerts which is limited to 100 by the API to avoid performance issues.
`alerts` counts alerts raised by your own engine in the last 24 hours. Alerts originating from the
CrowdSec community blocklist are excluded.
Allowed fields: `["alerts", "bans"]`.
@@ -19,5 +21,4 @@ widget:
url: http://crowdsechostorip:port
username: localhost # machine_id in crowdsec
password: password
limit24h: true # optional, limits alerts to last 24h. Default: false
```
+7 -2
View File
@@ -4,14 +4,19 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
const ALERTS_LIMIT = 500;
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: alerts, error: alertsError } = useWidgetAPI(widget, !!widget.limit24h ? "alerts24h" : "alerts");
const { data: alerts, error: alertsError } = useWidgetAPI(widget, "alerts");
const { data: bans, error: bansError } = useWidgetAPI(widget, "bans");
const alertsCount = alerts?.length ?? 0;
const alertsValue = t("common.number", { value: alertsCount });
if (alertsError || bansError) {
return <Container service={service} error={alertsError ?? bansError} />;
}
@@ -27,7 +32,7 @@ export default function Component({ service }) {
return (
<Container service={service}>
<Block label="crowdsec.alerts" value={t("common.number", { value: alerts?.length ?? 0 })} />
<Block label="crowdsec.alerts" value={alertsCount >= ALERTS_LIMIT ? `${alertsValue}+` : alertsValue} />
<Block label="crowdsec.bans" value={t("common.number", { value: bans?.length ?? 0 })} />
</Container>
);
+37 -3
View File
@@ -17,15 +17,25 @@ describe("widgets/crowdsec/component", () => {
vi.clearAllMocks();
});
it("selects alerts24h endpoint when limit24h is enabled", () => {
it("requests the alerts and bans endpoints", () => {
useWidgetAPI.mockImplementation(() => ({ data: undefined, error: undefined }));
renderWithProviders(<Component service={{ widget: { type: "crowdsec" } }} />, {
settings: { hideErrors: false },
});
expect(useWidgetAPI).toHaveBeenNthCalledWith(1, expect.any(Object), "alerts");
expect(useWidgetAPI).toHaveBeenNthCalledWith(2, expect.any(Object), "bans");
});
it("ignores the deprecated limit24h option", () => {
useWidgetAPI.mockImplementation(() => ({ data: undefined, error: undefined }));
renderWithProviders(<Component service={{ widget: { type: "crowdsec", limit24h: true } }} />, {
settings: { hideErrors: false },
});
expect(useWidgetAPI).toHaveBeenNthCalledWith(1, expect.any(Object), "alerts24h");
expect(useWidgetAPI).toHaveBeenNthCalledWith(2, expect.any(Object), "bans");
expect(useWidgetAPI).toHaveBeenNthCalledWith(1, expect.any(Object), "alerts");
});
it("renders placeholders when both alerts and bans are missing", () => {
@@ -52,4 +62,28 @@ describe("widgets/crowdsec/component", () => {
expectBlockValue(container, "crowdsec.alerts", 0);
expectBlockValue(container, "crowdsec.bans", 0);
});
it("marks the alert count as truncated at the API limit", () => {
useWidgetAPI
.mockReturnValueOnce({ data: new Array(500).fill({}), error: undefined })
.mockReturnValueOnce({ data: [], error: undefined });
renderWithProviders(<Component service={{ widget: { type: "crowdsec" } }} />, {
settings: { hideErrors: false },
});
expect(screen.getByText("500+")).toBeInTheDocument();
});
it("does not mark the alert count below the API limit", () => {
useWidgetAPI
.mockReturnValueOnce({ data: new Array(499).fill({}), error: undefined })
.mockReturnValueOnce({ data: [], error: undefined });
const { container } = renderWithProviders(<Component service={{ widget: { type: "crowdsec" } }} />, {
settings: { hideErrors: false },
});
expectBlockValue(container, "crowdsec.alerts", 499);
});
});
+2 -5
View File
@@ -7,13 +7,10 @@ const widget = {
mappings: {
alerts: {
endpoint: "alerts",
},
alerts24h: {
endpoint: "alerts?limit=0&since=24h",
endpoint: "alerts?limit=500&since=24h&with_decisions=false&include_capi=false",
},
bans: {
endpoint: "alerts?decision_type=ban&origin=crowdsec&has_active_decision=1",
endpoint: "alerts?decision_type=ban&include_capi=false&has_active_decision=1&with_decisions=false",
},
},
};