client-side data padding

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer
2025-06-06 03:57:33 +02:00
parent 41f5d42ba9
commit b046181947
6 changed files with 50 additions and 18 deletions

View File

@ -48,7 +48,10 @@ export class AdminLoginAuthorizeChart extends AKChart<EventVolume[]> {
cubicInterpolationMode: "monotone", cubicInterpolationMode: "monotone",
tension: 0.4, tension: 0.4,
}); });
return this.eventVolume(data, optsMap); return this.eventVolume(data, {
optsMap: optsMap,
padToDays: 7,
});
} }
} }

View File

@ -27,14 +27,14 @@ export class AdminModelPerDay extends AKChart<EventVolume[]> {
async apiRequest(): Promise<EventVolume[]> { async apiRequest(): Promise<EventVolume[]> {
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({ return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
action: this.action, action: this.action,
historyDays: 30,
...this.query, ...this.query,
}); });
} }
getChartData(data: EventVolume[]): ChartData { getChartData(data: EventVolume[]): ChartData {
return this.eventVolume( return this.eventVolume(data, {
data, optsMap: new Map([
new Map([
[ [
this.action, this.action,
{ {
@ -44,7 +44,8 @@ export class AdminModelPerDay extends AKChart<EventVolume[]> {
}, },
], ],
]), ]),
); padToDays: 30,
});
} }
} }

View File

@ -20,9 +20,8 @@ export class ApplicationAuthorizeChart extends AKChart<EventVolume[]> {
} }
getChartData(data: EventVolume[]): ChartData { getChartData(data: EventVolume[]): ChartData {
return this.eventVolume( return this.eventVolume(data, {
data, optsMap: new Map([
new Map([
[ [
EventActions.AuthorizeApplication, EventActions.AuthorizeApplication,
{ {
@ -32,7 +31,8 @@ export class ApplicationAuthorizeChart extends AKChart<EventVolume[]> {
}, },
], ],
]), ]),
); padToDays: 7,
});
} }
} }

View File

@ -38,7 +38,9 @@ export class EventVolumeChart extends AKChart<EventVolume[]> {
} }
getChartData(data: EventVolume[]): ChartData { getChartData(data: EventVolume[]): ChartData {
return this.eventVolume(data); return this.eventVolume(data, {
padToDays: 14,
});
} }
render(): TemplateResult { render(): TemplateResult {

View File

@ -24,9 +24,8 @@ export class UserChart extends AKChart<EventVolume[]> {
} }
getChartData(data: EventVolume[]): ChartData { getChartData(data: EventVolume[]): ChartData {
return this.eventVolume( return this.eventVolume(data, {
data, optsMap: new Map([
new Map([
[ [
EventActions.LoginFailed, EventActions.LoginFailed,
{ {
@ -52,7 +51,8 @@ export class UserChart extends AKChart<EventVolume[]> {
}, },
], ],
]), ]),
); padToDays: 7,
});
} }
} }

View File

@ -241,13 +241,19 @@ export abstract class AKChart<T> extends AKElement {
eventVolume( eventVolume(
data: EventVolume[], data: EventVolume[],
optsMap?: Map<EventActions, Partial<ChartDataset>>, options?: {
optsMap?: Map<EventActions, Partial<ChartDataset>>;
padToDays?: number;
},
): ChartData { ): ChartData {
const datasets: ChartData = { const datasets: ChartData = {
datasets: [], datasets: [],
}; };
if (!optsMap) { if (!options) {
optsMap = new Map<EventActions, Partial<ChartDataset>>(); options = {};
}
if (!options.optsMap) {
options.optsMap = new Map<EventActions, Partial<ChartDataset>>();
} }
const actions = new Set(data.map((v) => v.action)); const actions = new Set(data.map((v) => v.action));
actions.forEach((action) => { actions.forEach((action) => {
@ -258,11 +264,31 @@ export abstract class AKChart<T> extends AKElement {
y: v.count, 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.day)
.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({ datasets.datasets.push({
data: actionData, data: actionData,
label: actionToLabel(action), label: actionToLabel(action),
backgroundColor: getColorFromString(action).toString(), backgroundColor: getColorFromString(action).toString(),
...optsMap.get(action), ...options.optsMap?.get(action),
}); });
}); });
return datasets; return datasets;