Enhancement: add authorizationsLast24H field to Authentik widget, opt… (#6989)
Docker CI / Docker Build & Push (push) Has been cancelled
Lint / Linting Checks (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Release Drafter / Auto Label PR (push) Has been cancelled
Tests / vitest (1) (push) Has been cancelled
Tests / vitest (2) (push) Has been cancelled
Tests / vitest (3) (push) Has been cancelled
Tests / vitest (4) (push) Has been cancelled

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
Zhelyan Radoev
2026-08-16 07:21:51 -07:00
committed by GitHub
co-authored by shamoon
parent 198c86fb49
commit 600c9eee78
5 changed files with 57 additions and 25 deletions
+38 -12
View File
@@ -8,32 +8,40 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const isV2 = widget.version === 2;
const { data: usersData, error: usersError } = useWidgetAPI(widget, "users");
const loginsEndpoint = widget.version === 2 ? "loginv2" : "login";
const loginsEndpoint = isV2 ? "" : "login";
const { data: loginsData, error: loginsError } = useWidgetAPI(widget, loginsEndpoint);
const failedLoginsEndpoint = widget.version === 2 ? "login_failedv2" : "login_failed";
const failedLoginsEndpoint = isV2 ? "" : "login_failed";
const { data: failedLoginsData, error: failedLoginsError } = useWidgetAPI(widget, failedLoginsEndpoint);
if (usersError || loginsError || failedLoginsError) {
const finalError = usersError ?? loginsError ?? failedLoginsError;
const eventsDataEndpoint = isV2 ? "datav2" : "";
const { data: eventsData, error: eventsDataError } = useWidgetAPI(widget, eventsDataEndpoint);
if (usersError || loginsError || failedLoginsError || eventsDataError) {
const finalError = usersError ?? loginsError ?? failedLoginsError ?? eventsDataError;
return <Container service={service} error={finalError} />;
}
if (!usersData || !loginsData || !failedLoginsData) {
const hasNoData = isV2 ? !usersData || !eventsData : !usersData || !loginsData || !failedLoginsData;
if (hasNoData) {
return (
<Container service={service}>
<Block label="authentik.users" />
<Block label="authentik.loginsLast24H" />
<Block label="authentik.failedLoginsLast24H" />
{isV2 && <Block label="authentik.authorizationsLast24H" />}
</Container>
);
}
let loginsLast24H;
let failedLoginsLast24H;
let authorizationsLast24H;
switch (widget.version) {
// v1 is default
default:
@@ -48,13 +56,28 @@ export default function Component({ service }) {
);
break;
case 2:
loginsLast24H =
loginsData.reduce?.(
(total, current) => (current?.count && current?.action === "login" ? total + current.count : total),
0,
) || 0;
failedLoginsLast24H =
failedLoginsData.reduce?.((total, current) => (current?.count ? total + current.count : total), 0) || 0;
const events = Array.isArray(eventsData) ? eventsData : [];
const result = events.reduce(
(acc, current) => {
if (!current?.count || !current?.action) {
return acc;
}
if (current.action === "login") {
acc.logins += current.count;
} else if (current.action === "login_failed") {
acc.failed += current.count;
} else if (current.action === "authorize_application") {
acc.authorizations += current.count;
}
return acc;
},
{ logins: 0, failed: 0, authorizations: 0 },
);
loginsLast24H = result.logins;
failedLoginsLast24H = result.failed;
authorizationsLast24H = result.authorizations;
break;
}
@@ -63,6 +86,9 @@ export default function Component({ service }) {
<Block label="authentik.users" value={t("common.number", { value: usersData.pagination.count })} />
<Block label="authentik.loginsLast24H" value={t("common.number", { value: loginsLast24H })} />
<Block label="authentik.failedLoginsLast24H" value={t("common.number", { value: failedLoginsLast24H })} />
{isV2 && (
<Block label="authentik.authorizationsLast24H" value={t("common.number", { value: authorizationsLast24H })} />
)}
</Container>
);
}
+13 -5
View File
@@ -30,24 +30,31 @@ describe("widgets/authentik/component", () => {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expect(screen.getByText("authentik.users")).toBeInTheDocument();
expect(screen.getByText("authentik.loginsLast24H")).toBeInTheDocument();
expect(screen.getByText("authentik.failedLoginsLast24H")).toBeInTheDocument();
expect(screen.getByText("authentik.authorizationsLast24H")).toBeInTheDocument();
});
it("computes v2 login/failed counts from action data", () => {
useWidgetAPI.mockImplementation((widget, endpoint) => {
if (endpoint === "users") return { data: { pagination: { count: 10 } }, error: undefined };
if (endpoint === "loginv2")
if (endpoint === "datav2")
return {
data: [
{ action: "login", count: 2 },
{ action: "login", count: 4 },
{ action: "logout", count: 9 },
{ action: "login_failed", count: 1 },
{ action: "login_failed", count: 2 },
{ action: "authorize_application", count: 10 },
{ action: "authorize_application", count: 8 },
{ action: null, count: null },
{ action: "login", count: null },
],
error: undefined,
};
if (endpoint === "login_failedv2") return { data: [{ count: 3 }, { count: null }], error: undefined };
return { data: undefined, error: undefined };
});
@@ -55,10 +62,11 @@ describe("widgets/authentik/component", () => {
settings: { hideErrors: false },
});
expect(container.querySelectorAll(".service-block")).toHaveLength(3);
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
expectBlockValue(container, "authentik.users", 10);
expectBlockValue(container, "authentik.loginsLast24H", 2);
expectBlockValue(container, "authentik.loginsLast24H", 6);
expectBlockValue(container, "authentik.failedLoginsLast24H", 3);
expectBlockValue(container, "authentik.authorizationsLast24H", 18);
});
it("computes v1 login/failed counts for entries within the last 24h window", () => {
+2 -5
View File
@@ -11,14 +11,11 @@ const widget = {
login: {
endpoint: "events/events/per_month/?action=login",
},
loginv2: {
endpoint: "events/events/volume/?action=login&&history_days=1",
},
login_failed: {
endpoint: "events/events/per_month/?action=login_failed",
},
login_failedv2: {
endpoint: "events/events/volume/?action=login_failed&&history_days=1",
datav2: {
endpoint: "events/events/volume/?actions=login&actions=login_failed&actions=authorize_application&history_days=1",
},
},
};