add event-to-color map
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -1,14 +1,14 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
|
||||||
import { ChartData, ChartDataset } from "chart.js";
|
import { ChartData, ChartDataset } from "chart.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
|
|
||||||
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
||||||
|
import { EventChart } from "#elements/charts/EventChart";
|
||||||
|
|
||||||
@customElement("ak-charts-admin-login-authorization")
|
@customElement("ak-charts-admin-login-authorization")
|
||||||
export class AdminLoginAuthorizeChart extends AKChart<EventVolume[]> {
|
export class AdminLoginAuthorizeChart extends EventChart {
|
||||||
async apiRequest(): Promise<EventVolume[]> {
|
async apiRequest(): Promise<EventVolume[]> {
|
||||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
|
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
|
||||||
actions: [
|
actions: [
|
||||||
@ -23,8 +23,6 @@ export class AdminLoginAuthorizeChart extends AKChart<EventVolume[]> {
|
|||||||
const optsMap = new Map<EventActions, Partial<ChartDataset>>();
|
const optsMap = new Map<EventActions, Partial<ChartDataset>>();
|
||||||
optsMap.set(EventActions.AuthorizeApplication, {
|
optsMap.set(EventActions.AuthorizeApplication, {
|
||||||
label: msg("Authorizations"),
|
label: msg("Authorizations"),
|
||||||
backgroundColor: "rgba(43, 154, 243, 0.5)",
|
|
||||||
borderColor: "rgba(43, 154, 243, 1)",
|
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
fill: "origin",
|
fill: "origin",
|
||||||
cubicInterpolationMode: "monotone",
|
cubicInterpolationMode: "monotone",
|
||||||
@ -32,8 +30,6 @@ export class AdminLoginAuthorizeChart extends AKChart<EventVolume[]> {
|
|||||||
});
|
});
|
||||||
optsMap.set(EventActions.Login, {
|
optsMap.set(EventActions.Login, {
|
||||||
label: msg("Successful Logins"),
|
label: msg("Successful Logins"),
|
||||||
backgroundColor: "rgba(62, 134, 53, 0.5)",
|
|
||||||
borderColor: "rgba(62, 134, 53, 1)",
|
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
fill: "origin",
|
fill: "origin",
|
||||||
cubicInterpolationMode: "monotone",
|
cubicInterpolationMode: "monotone",
|
||||||
@ -41,8 +37,6 @@ export class AdminLoginAuthorizeChart extends AKChart<EventVolume[]> {
|
|||||||
});
|
});
|
||||||
optsMap.set(EventActions.LoginFailed, {
|
optsMap.set(EventActions.LoginFailed, {
|
||||||
label: msg("Failed Logins"),
|
label: msg("Failed Logins"),
|
||||||
backgroundColor: "rgba(201, 24, 11, 0.5)",
|
|
||||||
borderColor: "rgba(201, 24, 11, 1)",
|
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
fill: "origin",
|
fill: "origin",
|
||||||
cubicInterpolationMode: "monotone",
|
cubicInterpolationMode: "monotone",
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
|
||||||
import { ChartData } from "chart.js";
|
import { ChartData } from "chart.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
@ -11,9 +10,10 @@ import {
|
|||||||
EventsApi,
|
EventsApi,
|
||||||
EventsEventsVolumeListRequest,
|
EventsEventsVolumeListRequest,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
import { EventChart } from "#elements/charts/EventChart";
|
||||||
|
|
||||||
@customElement("ak-charts-admin-model-per-day")
|
@customElement("ak-charts-admin-model-per-day")
|
||||||
export class AdminModelPerDay extends AKChart<EventVolume[]> {
|
export class AdminModelPerDay extends EventChart {
|
||||||
@property()
|
@property()
|
||||||
action: EventActions = EventActions.ModelCreated;
|
action: EventActions = EventActions.ModelCreated;
|
||||||
|
|
||||||
@ -38,7 +38,6 @@ export class AdminModelPerDay extends AKChart<EventVolume[]> {
|
|||||||
this.action,
|
this.action,
|
||||||
{
|
{
|
||||||
label: this.label || msg("Objects created"),
|
label: this.label || msg("Objects created"),
|
||||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
|
||||||
import { ChartData } from "chart.js";
|
import { ChartData } from "chart.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
||||||
|
import { EventChart } from "#elements/charts/EventChart";
|
||||||
|
|
||||||
@customElement("ak-charts-application-authorize")
|
@customElement("ak-charts-application-authorize")
|
||||||
export class ApplicationAuthorizeChart extends AKChart<EventVolume[]> {
|
export class ApplicationAuthorizeChart extends EventChart {
|
||||||
@property({ attribute: "application-id" })
|
@property({ attribute: "application-id" })
|
||||||
applicationId!: string;
|
applicationId!: string;
|
||||||
|
|
||||||
@ -26,7 +26,6 @@ export class ApplicationAuthorizeChart extends AKChart<EventVolume[]> {
|
|||||||
EventActions.AuthorizeApplication,
|
EventActions.AuthorizeApplication,
|
||||||
{
|
{
|
||||||
label: msg("Authorizations"),
|
label: msg("Authorizations"),
|
||||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
|
||||||
import { ChartData } from "chart.js";
|
import { ChartData } from "chart.js";
|
||||||
|
|
||||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
@ -8,9 +7,10 @@ import { customElement, property } from "lit/decorators.js";
|
|||||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||||
|
|
||||||
import { EventVolume, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
|
import { EventVolume, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
|
||||||
|
import { EventChart } from "#elements/charts/EventChart";
|
||||||
|
|
||||||
@customElement("ak-events-volume-chart")
|
@customElement("ak-events-volume-chart")
|
||||||
export class EventVolumeChart extends AKChart<EventVolume[]> {
|
export class EventVolumeChart extends EventChart {
|
||||||
_query?: EventsEventsListRequest;
|
_query?: EventsEventsListRequest;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
|
||||||
import { ChartData } from "chart.js";
|
import { ChartData } from "chart.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
import { EventActions, EventVolume, EventsApi } from "@goauthentik/api";
|
||||||
|
import { EventChart } from "#elements/charts/EventChart";
|
||||||
|
|
||||||
@customElement("ak-charts-user")
|
@customElement("ak-charts-user")
|
||||||
export class UserChart extends AKChart<EventVolume[]> {
|
export class UserChart extends EventChart {
|
||||||
@property()
|
@property()
|
||||||
username?: string;
|
username?: string;
|
||||||
|
|
||||||
@ -30,7 +30,6 @@ export class UserChart extends AKChart<EventVolume[]> {
|
|||||||
EventActions.LoginFailed,
|
EventActions.LoginFailed,
|
||||||
{
|
{
|
||||||
label: msg("Failed Logins"),
|
label: msg("Failed Logins"),
|
||||||
backgroundColor: "rgba(201, 25, 11, .5)",
|
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -38,7 +37,6 @@ export class UserChart extends AKChart<EventVolume[]> {
|
|||||||
EventActions.Login,
|
EventActions.Login,
|
||||||
{
|
{
|
||||||
label: msg("Successful Logins"),
|
label: msg("Successful Logins"),
|
||||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -46,7 +44,6 @@ export class UserChart extends AKChart<EventVolume[]> {
|
|||||||
EventActions.AuthorizeApplication,
|
EventActions.AuthorizeApplication,
|
||||||
{
|
{
|
||||||
label: msg("Application authorizations"),
|
label: msg("Application authorizations"),
|
||||||
backgroundColor: "rgba(43, 154, 243, .5)",
|
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { actionToLabel } from "#common/labels";
|
|
||||||
import { EVENT_REFRESH, EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH, EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
|
||||||
import {
|
import {
|
||||||
APIError,
|
APIError,
|
||||||
@ -30,7 +29,7 @@ import { msg } from "@lit/localize";
|
|||||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
import { property, state } from "lit/decorators.js";
|
import { property, state } from "lit/decorators.js";
|
||||||
|
|
||||||
import { EventActions, EventVolume, UiThemeEnum } from "@goauthentik/api";
|
import { UiThemeEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
Chart.register(Legend, Tooltip);
|
Chart.register(Legend, Tooltip);
|
||||||
Chart.register(LineController, BarController, DoughnutController);
|
Chart.register(LineController, BarController, DoughnutController);
|
||||||
@ -40,32 +39,6 @@ Chart.register(TimeScale, TimeSeriesScale, LinearScale, Filler);
|
|||||||
export const FONT_COLOUR_DARK_MODE = "#fafafa";
|
export const FONT_COLOUR_DARK_MODE = "#fafafa";
|
||||||
export const FONT_COLOUR_LIGHT_MODE = "#151515";
|
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 {
|
export abstract class AKChart<T> extends AKElement {
|
||||||
abstract apiRequest(): Promise<T>;
|
abstract apiRequest(): Promise<T>;
|
||||||
abstract getChartData(data: T): ChartData;
|
abstract getChartData(data: T): ChartData;
|
||||||
@ -238,59 +211,4 @@ export abstract class AKChart<T> extends AKElement {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
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: getColorFromString(action).toString(),
|
|
||||||
...options.optsMap?.get(action),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return datasets;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
122
web/src/elements/charts/EventChart.ts
Normal file
122
web/src/elements/charts/EventChart.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
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 "#4cb140";
|
||||||
|
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 "#23511e";
|
||||||
|
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