events: rework metrics endpoint (#14934)
* rework event volume Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * the rest of the owl Signed-off-by: Jens Langhammer <jens@goauthentik.io> * client-side data padding Signed-off-by: Jens Langhammer <jens@goauthentik.io> * I love deleting code Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix clamping Signed-off-by: Jens Langhammer <jens@goauthentik.io> * chunk it Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add event-to-color map Signed-off-by: Jens Langhammer <jens@goauthentik.io> * sync colours Signed-off-by: Jens Langhammer <jens@goauthentik.io> * switch colours Signed-off-by: Jens Langhammer <jens@goauthentik.io> * heatmap? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Revert "heatmap?" This reverts commitc1f549a18b. * Revert "Revert "heatmap?"" This reverts commit6d6175b96b. * Revert "Revert "Revert "heatmap?""" This reverts commit3717903f12. * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -13,7 +13,7 @@ import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
|
||||
import { EventActions } from "@goauthentik/api";
|
||||
import { EventActions, EventsEventsVolumeListRequest } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-admin-dashboard-users")
|
||||
export class DashboardUserPage extends AKElement {
|
||||
@ -46,9 +46,9 @@ export class DashboardUserPage extends AKElement {
|
||||
<ak-aggregate-card header=${msg("Users created per day in the last month")}>
|
||||
<ak-charts-admin-model-per-day
|
||||
.query=${{
|
||||
context__model__app: "authentik_core",
|
||||
context__model__model_name: "user",
|
||||
}}
|
||||
contextModelApp: "authentik_core",
|
||||
contextModelName: "user",
|
||||
} as EventsEventsVolumeListRequest}
|
||||
label=${msg("Users created")}
|
||||
>
|
||||
</ak-charts-admin-model-per-day>
|
||||
|
||||
@ -1,68 +1,51 @@
|
||||
import { EventChart } from "#elements/charts/EventChart";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart, RGBAColor } from "@goauthentik/elements/charts/Chart";
|
||||
import { ChartData } from "chart.js";
|
||||
import { ChartData, ChartDataset } from "chart.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { AdminApi, LoginMetrics } from "@goauthentik/api";
|
||||
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-charts-admin-login-authorization")
|
||||
export class AdminLoginAuthorizeChart extends AKChart<LoginMetrics> {
|
||||
async apiRequest(): Promise<LoginMetrics> {
|
||||
return new AdminApi(DEFAULT_CONFIG).adminMetricsRetrieve();
|
||||
export class AdminLoginAuthorizeChart extends EventChart {
|
||||
async apiRequest(): Promise<EventVolume[]> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
|
||||
actions: [
|
||||
EventActions.AuthorizeApplication,
|
||||
EventActions.Login,
|
||||
EventActions.LoginFailed,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
getChartData(data: LoginMetrics): ChartData {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: msg("Authorizations"),
|
||||
backgroundColor: new RGBAColor(43, 154, 243, 0.5).toString(),
|
||||
borderColor: new RGBAColor(43, 154, 243, 1).toString(),
|
||||
spanGaps: true,
|
||||
fill: "origin",
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
data: data.authorizations.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord,
|
||||
y: cord.yCord,
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: msg("Failed Logins"),
|
||||
backgroundColor: new RGBAColor(201, 24, 11, 0.5).toString(),
|
||||
borderColor: new RGBAColor(201, 24, 11, 1).toString(),
|
||||
spanGaps: true,
|
||||
fill: "origin",
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
data: data.loginsFailed.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord,
|
||||
y: cord.yCord,
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: msg("Successful Logins"),
|
||||
backgroundColor: new RGBAColor(62, 134, 53, 0.5).toString(),
|
||||
borderColor: new RGBAColor(62, 134, 53, 1).toString(),
|
||||
spanGaps: true,
|
||||
fill: "origin",
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
data: data.logins.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord,
|
||||
y: cord.yCord,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
getChartData(data: EventVolume[]): ChartData {
|
||||
const optsMap = new Map<EventActions, Partial<ChartDataset>>();
|
||||
optsMap.set(EventActions.AuthorizeApplication, {
|
||||
label: msg("Authorizations"),
|
||||
spanGaps: true,
|
||||
fill: "origin",
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
});
|
||||
optsMap.set(EventActions.Login, {
|
||||
label: msg("Successful Logins"),
|
||||
spanGaps: true,
|
||||
fill: "origin",
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
});
|
||||
optsMap.set(EventActions.LoginFailed, {
|
||||
label: msg("Failed Logins"),
|
||||
spanGaps: true,
|
||||
fill: "origin",
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
});
|
||||
return this.eventVolume(data, {
|
||||
optsMap: optsMap,
|
||||
padToDays: 7,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { EventChart } from "#elements/charts/EventChart";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import { ChartData, Tick } from "chart.js";
|
||||
import { ChartData } from "chart.js";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { Coordinate, EventActions, EventsApi } from "@goauthentik/api";
|
||||
import {
|
||||
EventActions,
|
||||
EventVolume,
|
||||
EventsApi,
|
||||
EventsEventsVolumeListRequest,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-charts-admin-model-per-day")
|
||||
export class AdminModelPerDay extends AKChart<Coordinate[]> {
|
||||
export class AdminModelPerDay extends EventChart {
|
||||
@property()
|
||||
action: EventActions = EventActions.ModelCreated;
|
||||
|
||||
@ -16,39 +21,29 @@ export class AdminModelPerDay extends AKChart<Coordinate[]> {
|
||||
label?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
query?: { [key: string]: unknown } | undefined;
|
||||
query?: EventsEventsVolumeListRequest;
|
||||
|
||||
async apiRequest(): Promise<Coordinate[]> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsPerMonthList({
|
||||
async apiRequest(): Promise<EventVolume[]> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
|
||||
action: this.action,
|
||||
query: JSON.stringify(this.query || {}),
|
||||
historyDays: 30,
|
||||
...this.query,
|
||||
});
|
||||
}
|
||||
|
||||
timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string {
|
||||
const valueStamp = ticks[index];
|
||||
const delta = Date.now() - valueStamp.value;
|
||||
const ago = Math.round(delta / 1000 / 3600 / 24);
|
||||
return msg(str`${ago} days ago`);
|
||||
}
|
||||
|
||||
getChartData(data: Coordinate[]): ChartData {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: this.label || msg("Objects created"),
|
||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
||||
spanGaps: true,
|
||||
data:
|
||||
data.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord || 0,
|
||||
y: cord.yCord || 0,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
],
|
||||
};
|
||||
getChartData(data: EventVolume[]): ChartData {
|
||||
return this.eventVolume(data, {
|
||||
optsMap: new Map([
|
||||
[
|
||||
this.action,
|
||||
{
|
||||
label: this.label || msg("Objects created"),
|
||||
spanGaps: true,
|
||||
},
|
||||
],
|
||||
]),
|
||||
padToDays: 30,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { actionToColor } from "#elements/charts/EventChart";
|
||||
import { SummarizedSyncStatus } from "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
@ -7,7 +8,7 @@ import { ChartData, ChartOptions } from "chart.js";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { OutpostsApi } from "@goauthentik/api";
|
||||
import { EventActions, OutpostsApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-admin-status-chart-outpost")
|
||||
export class OutpostStatusChart extends AKChart<SummarizedSyncStatus[]> {
|
||||
@ -65,7 +66,11 @@ export class OutpostStatusChart extends AKChart<SummarizedSyncStatus[]> {
|
||||
labels: [msg("Healthy outposts"), msg("Outdated outposts"), msg("Unhealthy outposts")],
|
||||
datasets: data.map((d) => {
|
||||
return {
|
||||
backgroundColor: ["#3e8635", "#C9190B", "#2b9af3"],
|
||||
backgroundColor: [
|
||||
actionToColor(EventActions.Login),
|
||||
actionToColor(EventActions.SuspiciousRequest),
|
||||
actionToColor(EventActions.AuthorizeApplication),
|
||||
],
|
||||
spanGaps: true,
|
||||
data: [d.healthy, d.failed, d.unsynced],
|
||||
label: d.label,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { actionToColor } from "#elements/charts/EventChart";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import "@goauthentik/elements/forms/ConfirmationForm";
|
||||
@ -7,7 +8,13 @@ import { ChartData, ChartOptions } from "chart.js";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { ProvidersApi, SourcesApi, SyncStatus, SystemTaskStatusEnum } from "@goauthentik/api";
|
||||
import {
|
||||
EventActions,
|
||||
ProvidersApi,
|
||||
SourcesApi,
|
||||
SyncStatus,
|
||||
SystemTaskStatusEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
export interface SummarizedSyncStatus {
|
||||
healthy: number;
|
||||
@ -136,7 +143,11 @@ export class SyncStatusChart extends AKChart<SummarizedSyncStatus[]> {
|
||||
labels: [msg("Healthy"), msg("Failed"), msg("Unsynced / N/A")],
|
||||
datasets: data.map((d) => {
|
||||
return {
|
||||
backgroundColor: ["#3e8635", "#C9190B", "#2b9af3"],
|
||||
backgroundColor: [
|
||||
actionToColor(EventActions.Login),
|
||||
actionToColor(EventActions.SuspiciousRequest),
|
||||
actionToColor(EventActions.AuthorizeApplication),
|
||||
],
|
||||
spanGaps: true,
|
||||
data: [d.healthy, d.failed, d.unsynced],
|
||||
label: d.label,
|
||||
|
||||
@ -1,47 +1,37 @@
|
||||
import { EventChart } from "#elements/charts/EventChart";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import { ChartData, Tick } from "chart.js";
|
||||
import { ChartData } from "chart.js";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { Coordinate, CoreApi } from "@goauthentik/api";
|
||||
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-charts-application-authorize")
|
||||
export class ApplicationAuthorizeChart extends AKChart<Coordinate[]> {
|
||||
@property()
|
||||
applicationSlug!: string;
|
||||
export class ApplicationAuthorizeChart extends EventChart {
|
||||
@property({ attribute: "application-id" })
|
||||
applicationId!: string;
|
||||
|
||||
async apiRequest(): Promise<Coordinate[]> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreApplicationsMetricsList({
|
||||
slug: this.applicationSlug,
|
||||
async apiRequest(): Promise<EventVolume[]> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
|
||||
action: EventActions.AuthorizeApplication,
|
||||
contextAuthorizedApp: this.applicationId.replaceAll("-", ""),
|
||||
});
|
||||
}
|
||||
|
||||
timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string {
|
||||
const valueStamp = ticks[index];
|
||||
const delta = Date.now() - valueStamp.value;
|
||||
const ago = Math.round(delta / 1000 / 3600 / 24);
|
||||
return msg(str`${ago} days ago`);
|
||||
}
|
||||
|
||||
getChartData(data: Coordinate[]): ChartData {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: msg("Authorizations"),
|
||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
||||
spanGaps: true,
|
||||
data:
|
||||
data.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord || 0,
|
||||
y: cord.yCord || 0,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
],
|
||||
};
|
||||
getChartData(data: EventVolume[]): ChartData {
|
||||
return this.eventVolume(data, {
|
||||
optsMap: new Map([
|
||||
[
|
||||
EventActions.AuthorizeApplication,
|
||||
{
|
||||
label: msg("Authorizations"),
|
||||
spanGaps: true,
|
||||
},
|
||||
],
|
||||
]),
|
||||
padToDays: 7,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -282,7 +282,7 @@ export class ApplicationViewPage extends AKElement {
|
||||
<div class="pf-c-card__body">
|
||||
${this.application &&
|
||||
html` <ak-charts-application-authorize
|
||||
applicationSlug=${this.application.slug}
|
||||
application-id=${this.application.pk}
|
||||
>
|
||||
</ak-charts-application-authorize>`}
|
||||
</div>
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import { EventChart } from "#elements/charts/EventChart";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import { ChartData } from "chart.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
|
||||
import { Coordinate, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
|
||||
import { EventVolume, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-events-volume-chart")
|
||||
export class EventVolumeChart extends AKChart<Coordinate[]> {
|
||||
export class EventVolumeChart extends EventChart {
|
||||
_query?: EventsEventsListRequest;
|
||||
|
||||
@property({ attribute: false })
|
||||
set query(value: EventsEventsListRequest | undefined) {
|
||||
if (JSON.stringify(this._query) === JSON.stringify(value)) return;
|
||||
this._query = value;
|
||||
this.refreshHandler();
|
||||
}
|
||||
@ -24,39 +24,28 @@ export class EventVolumeChart extends AKChart<Coordinate[]> {
|
||||
return super.styles.concat(
|
||||
PFCard,
|
||||
css`
|
||||
.pf-c-card__body {
|
||||
height: 12rem;
|
||||
.pf-c-card {
|
||||
height: 20rem;
|
||||
}
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
apiRequest(): Promise<Coordinate[]> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList(this._query);
|
||||
apiRequest(): Promise<EventVolume[]> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
|
||||
historyDays: 7,
|
||||
...this._query,
|
||||
});
|
||||
}
|
||||
|
||||
getChartData(data: Coordinate[]): ChartData {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: msg("Events"),
|
||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
||||
spanGaps: true,
|
||||
data:
|
||||
data.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord || 0,
|
||||
y: cord.yCord || 0,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
],
|
||||
};
|
||||
getChartData(data: EventVolume[]): ChartData {
|
||||
return this.eventVolume(data, {
|
||||
padToDays: 7,
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">${msg("Event volume")}</div>
|
||||
<div class="pf-c-card__body">${super.render()}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@ -1,71 +1,55 @@
|
||||
import { EventChart } from "#elements/charts/EventChart";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import { ChartData, Tick } from "chart.js";
|
||||
import { ChartData } from "chart.js";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { CoreApi, UserMetrics } from "@goauthentik/api";
|
||||
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-charts-user")
|
||||
export class UserChart extends AKChart<UserMetrics> {
|
||||
@property({ type: Number })
|
||||
userId?: number;
|
||||
export class UserChart extends EventChart {
|
||||
@property()
|
||||
username?: string;
|
||||
|
||||
async apiRequest(): Promise<UserMetrics> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreUsersMetricsRetrieve({
|
||||
id: this.userId || 0,
|
||||
async apiRequest(): Promise<EventVolume[]> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
|
||||
actions: [
|
||||
EventActions.Login,
|
||||
EventActions.LoginFailed,
|
||||
EventActions.AuthorizeApplication,
|
||||
],
|
||||
username: this.username,
|
||||
});
|
||||
}
|
||||
|
||||
timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string {
|
||||
const valueStamp = ticks[index];
|
||||
const delta = Date.now() - valueStamp.value;
|
||||
const ago = Math.round(delta / 1000 / 3600 / 24);
|
||||
return msg(str`${ago} days ago`);
|
||||
}
|
||||
|
||||
getChartData(data: UserMetrics): ChartData {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: msg("Failed Logins"),
|
||||
backgroundColor: "rgba(201, 25, 11, .5)",
|
||||
spanGaps: true,
|
||||
data:
|
||||
data.loginsFailed?.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord || 0,
|
||||
y: cord.yCord || 0,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
{
|
||||
label: msg("Successful Logins"),
|
||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
||||
spanGaps: true,
|
||||
data:
|
||||
data.logins?.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord || 0,
|
||||
y: cord.yCord || 0,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
{
|
||||
label: msg("Application authorizations"),
|
||||
backgroundColor: "rgba(43, 154, 243, .5)",
|
||||
spanGaps: true,
|
||||
data:
|
||||
data.authorizations?.map((cord) => {
|
||||
return {
|
||||
x: cord.xCord || 0,
|
||||
y: cord.yCord || 0,
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
],
|
||||
};
|
||||
getChartData(data: EventVolume[]): ChartData {
|
||||
return this.eventVolume(data, {
|
||||
optsMap: new Map([
|
||||
[
|
||||
EventActions.LoginFailed,
|
||||
{
|
||||
label: msg("Failed Logins"),
|
||||
spanGaps: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
EventActions.Login,
|
||||
{
|
||||
label: msg("Successful Logins"),
|
||||
spanGaps: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
EventActions.AuthorizeApplication,
|
||||
{
|
||||
label: msg("Application authorizations"),
|
||||
spanGaps: true,
|
||||
},
|
||||
],
|
||||
]),
|
||||
padToDays: 7,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -389,7 +389,7 @@ export class UserViewPage extends WithCapabilitiesConfig(AKElement) {
|
||||
${msg("Actions over the last week (per 8 hours)")}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-charts-user userId=${this.user.pk || 0}> </ak-charts-user>
|
||||
<ak-charts-user username=${this.user.username}> </ak-charts-user>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@ -21,7 +21,7 @@ import {
|
||||
import { Legend, Tooltip } from "chart.js";
|
||||
import { BarController, DoughnutController, LineController } from "chart.js";
|
||||
import { ArcElement, BarElement } from "chart.js";
|
||||
import { LinearScale, TimeScale } from "chart.js";
|
||||
import { LinearScale, TimeScale, TimeSeriesScale } from "chart.js";
|
||||
import "chartjs-adapter-date-fns";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@ -33,37 +33,11 @@ import { UiThemeEnum } from "@goauthentik/api";
|
||||
Chart.register(Legend, Tooltip);
|
||||
Chart.register(LineController, BarController, DoughnutController);
|
||||
Chart.register(ArcElement, BarElement, PointElement, LineElement);
|
||||
Chart.register(TimeScale, LinearScale, Filler);
|
||||
Chart.register(TimeScale, TimeSeriesScale, LinearScale, Filler);
|
||||
|
||||
export const FONT_COLOUR_DARK_MODE = "#fafafa";
|
||||
export const FONT_COLOUR_LIGHT_MODE = "#151515";
|
||||
|
||||
export class RGBAColor {
|
||||
constructor(
|
||||
public r: number,
|
||||
public g: number,
|
||||
public b: number,
|
||||
public a: number = 1,
|
||||
) {}
|
||||
toString(): string {
|
||||
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getColorFromString(stringInput: string): RGBAColor {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < stringInput.length; i++) {
|
||||
hash = stringInput.charCodeAt(i) + ((hash << 5) - hash);
|
||||
hash = hash & hash;
|
||||
}
|
||||
const rgb = [0, 0, 0];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 255;
|
||||
rgb[i] = value;
|
||||
}
|
||||
return new RGBAColor(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
export abstract class AKChart<T> extends AKElement {
|
||||
abstract apiRequest(): Promise<T>;
|
||||
abstract getChartData(data: T): ChartData;
|
||||
@ -184,7 +158,7 @@ export abstract class AKChart<T> extends AKElement {
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
type: "timeseries",
|
||||
display: true,
|
||||
ticks: {
|
||||
callback: (tickValue: string | number, index: number, ticks: Tick[]) => {
|
||||
|
||||
120
web/src/elements/charts/EventChart.ts
Normal file
120
web/src/elements/charts/EventChart.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import { actionToLabel } from "#common/labels";
|
||||
import { AKChart } from "#elements/charts/Chart";
|
||||
import { ChartData, ChartDataset } from "chart.js";
|
||||
|
||||
import { EventActions, EventVolume } from "@goauthentik/api";
|
||||
|
||||
export function actionToColor(action: EventActions): string {
|
||||
switch (action) {
|
||||
case EventActions.AuthorizeApplication:
|
||||
return "#0060c0";
|
||||
case EventActions.ConfigurationError:
|
||||
return "#23511e";
|
||||
case EventActions.EmailSent:
|
||||
return "#009596";
|
||||
case EventActions.FlowExecution:
|
||||
return "#f4c145";
|
||||
case EventActions.ImpersonationEnded:
|
||||
return "#a2d9d9";
|
||||
case EventActions.ImpersonationStarted:
|
||||
return "#a2d9d9";
|
||||
case EventActions.InvitationUsed:
|
||||
return "#8bc1f7";
|
||||
case EventActions.Login:
|
||||
return "#4cb140";
|
||||
case EventActions.LoginFailed:
|
||||
return "#ec7a08";
|
||||
case EventActions.Logout:
|
||||
return "#f9e0a2";
|
||||
case EventActions.ModelCreated:
|
||||
return "#8f4700";
|
||||
case EventActions.ModelDeleted:
|
||||
return "#002f5d";
|
||||
case EventActions.ModelUpdated:
|
||||
return "#bde2b9";
|
||||
case EventActions.PasswordSet:
|
||||
return "#003737";
|
||||
case EventActions.PolicyException:
|
||||
return "#c58c00";
|
||||
case EventActions.PolicyExecution:
|
||||
return "#f4b678";
|
||||
case EventActions.PropertyMappingException:
|
||||
return "#519de9";
|
||||
case EventActions.SecretRotate:
|
||||
return "#38812f";
|
||||
case EventActions.SecretView:
|
||||
return "#73c5c5";
|
||||
case EventActions.SourceLinked:
|
||||
return "#f6d173";
|
||||
case EventActions.SuspiciousRequest:
|
||||
return "#c46100";
|
||||
case EventActions.SystemException:
|
||||
return "#004b95";
|
||||
case EventActions.SystemTaskException:
|
||||
return "#7cc674";
|
||||
case EventActions.SystemTaskExecution:
|
||||
return "#005f60";
|
||||
case EventActions.UpdateAvailable:
|
||||
return "#f0ab00";
|
||||
case EventActions.UserWrite:
|
||||
return "#ef9234";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export abstract class EventChart extends AKChart<EventVolume[]> {
|
||||
eventVolume(
|
||||
data: EventVolume[],
|
||||
options?: {
|
||||
optsMap?: Map<EventActions, Partial<ChartDataset>>;
|
||||
padToDays?: number;
|
||||
},
|
||||
): ChartData {
|
||||
const datasets: ChartData = {
|
||||
datasets: [],
|
||||
};
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (!options.optsMap) {
|
||||
options.optsMap = new Map<EventActions, Partial<ChartDataset>>();
|
||||
}
|
||||
const actions = new Set(data.map((v) => v.action));
|
||||
actions.forEach((action) => {
|
||||
const actionData: { x: number; y: number }[] = [];
|
||||
data.filter((v) => v.action === action).forEach((v) => {
|
||||
actionData.push({
|
||||
x: v.time.getTime(),
|
||||
y: v.count,
|
||||
});
|
||||
});
|
||||
// Check if we need to pad the data to reach a certain time window
|
||||
const earliestDate = data
|
||||
.filter((v) => v.action === action)
|
||||
.map((v) => v.time)
|
||||
.sort((a, b) => b.getTime() - a.getTime())
|
||||
.reverse();
|
||||
if (earliestDate.length > 0 && options.padToDays) {
|
||||
const earliestPadded = new Date(
|
||||
new Date().getTime() - options.padToDays * (1000 * 3600 * 24),
|
||||
);
|
||||
const daysDelta = Math.round(
|
||||
(earliestDate[0].getTime() - earliestPadded.getTime()) / (1000 * 3600 * 24),
|
||||
);
|
||||
if (daysDelta > 0) {
|
||||
actionData.push({
|
||||
x: earliestPadded.getTime(),
|
||||
y: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
datasets.datasets.push({
|
||||
data: actionData,
|
||||
label: actionToLabel(action),
|
||||
backgroundColor: actionToColor(action),
|
||||
...options.optsMap?.get(action),
|
||||
});
|
||||
});
|
||||
return datasets;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user