rework event volume
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -4,15 +4,16 @@ from datetime import timedelta
|
|||||||
from json import loads
|
from json import loads
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
from django.db.models.aggregates import Count
|
from django.db.models import Count
|
||||||
from django.db.models.fields.json import KeyTextTransform, KeyTransform
|
from django.db.models.fields.json import KeyTextTransform, KeyTransform
|
||||||
from django.db.models.functions import ExtractDay, ExtractHour
|
from django.db.models.functions import ExtractDay, TruncDate
|
||||||
from django.db.models.query_utils import Q
|
from django.db.models.query_utils import Q
|
||||||
|
from django.utils.timezone import now
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
from drf_spectacular.utils import OpenApiParameter, extend_schema, inline_serializer
|
||||||
from guardian.shortcuts import get_objects_for_user
|
from guardian.shortcuts import get_objects_for_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import DictField, IntegerField
|
from rest_framework.fields import CharField, DateField, DictField, IntegerField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
@ -156,13 +157,29 @@ class EventViewSet(ModelViewSet):
|
|||||||
return Response(EventTopPerUserSerializer(instance=events, many=True).data)
|
return Response(EventTopPerUserSerializer(instance=events, many=True).data)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={200: CoordinateSerializer(many=True)},
|
responses={
|
||||||
|
200: inline_serializer(
|
||||||
|
"EventVolume",
|
||||||
|
fields={
|
||||||
|
"action": CharField(),
|
||||||
|
"day": DateField(),
|
||||||
|
"count": IntegerField(),
|
||||||
|
},
|
||||||
|
many=True,
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
@action(detail=False, methods=["GET"], pagination_class=None)
|
@action(detail=False, methods=["GET"], pagination_class=None)
|
||||||
def volume(self, request: Request) -> Response:
|
def volume(self, request: Request) -> Response:
|
||||||
"""Get event volume for specified filters and timeframe"""
|
"""Get event volume for specified filters and timeframe"""
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
return Response(queryset.get_events_per(timedelta(days=7), ExtractHour, 7 * 3))
|
return Response(
|
||||||
|
queryset.filter(created__gte=now() - timedelta(days=14))
|
||||||
|
.annotate(day=TruncDate("created"))
|
||||||
|
.values("day", "action")
|
||||||
|
.annotate(count=Count("pk"))
|
||||||
|
.order_by("-day", "action")
|
||||||
|
)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={200: CoordinateSerializer(many=True)},
|
responses={200: CoordinateSerializer(many=True)},
|
||||||
|
|||||||
16
schema.yml
16
schema.yml
@ -7540,7 +7540,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Coordinate'
|
$ref: '#/components/schemas/EventVolume'
|
||||||
description: ''
|
description: ''
|
||||||
'400':
|
'400':
|
||||||
content:
|
content:
|
||||||
@ -44986,6 +44986,20 @@ components:
|
|||||||
- application
|
- application
|
||||||
- counted_events
|
- counted_events
|
||||||
- unique_users
|
- unique_users
|
||||||
|
EventVolume:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
action:
|
||||||
|
type: string
|
||||||
|
day:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- action
|
||||||
|
- count
|
||||||
|
- day
|
||||||
EventsRequestedEnum:
|
EventsRequestedEnum:
|
||||||
enum:
|
enum:
|
||||||
- https://schemas.openid.net/secevent/caep/event-type/session-revoked
|
- https://schemas.openid.net/secevent/caep/event-type/session-revoked
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AKChart, RGBAColor } from "@goauthentik/elements/charts/Chart";
|
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";
|
||||||
@ -18,8 +18,8 @@ export class AdminLoginAuthorizeChart extends AKChart<LoginMetrics> {
|
|||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: msg("Authorizations"),
|
label: msg("Authorizations"),
|
||||||
backgroundColor: new RGBAColor(43, 154, 243, 0.5).toString(),
|
backgroundColor: "rgba(43, 154, 243, 0.5)",
|
||||||
borderColor: new RGBAColor(43, 154, 243, 1).toString(),
|
borderColor: "rgba(43, 154, 243, 1)",
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
fill: "origin",
|
fill: "origin",
|
||||||
cubicInterpolationMode: "monotone",
|
cubicInterpolationMode: "monotone",
|
||||||
@ -33,8 +33,8 @@ export class AdminLoginAuthorizeChart extends AKChart<LoginMetrics> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: msg("Failed Logins"),
|
label: msg("Failed Logins"),
|
||||||
backgroundColor: new RGBAColor(201, 24, 11, 0.5).toString(),
|
backgroundColor: "rgba(201, 24, 11, 0.5)",
|
||||||
borderColor: new RGBAColor(201, 24, 11, 1).toString(),
|
borderColor: "rgba(201, 24, 11, 1)",
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
fill: "origin",
|
fill: "origin",
|
||||||
cubicInterpolationMode: "monotone",
|
cubicInterpolationMode: "monotone",
|
||||||
@ -48,8 +48,8 @@ export class AdminLoginAuthorizeChart extends AKChart<LoginMetrics> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: msg("Successful Logins"),
|
label: msg("Successful Logins"),
|
||||||
backgroundColor: new RGBAColor(62, 134, 53, 0.5).toString(),
|
backgroundColor: "rgba(62, 134, 53, 0.5)",
|
||||||
borderColor: new RGBAColor(62, 134, 53, 1).toString(),
|
borderColor: "rgba(62, 134, 53, 1)",
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
fill: "origin",
|
fill: "origin",
|
||||||
cubicInterpolationMode: "monotone",
|
cubicInterpolationMode: "monotone",
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
|
import { actionToLabel } from "#common/labels";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
import { AKChart, getColorFromString } from "@goauthentik/elements/charts/Chart";
|
||||||
import { ChartData } from "chart.js";
|
import { ChartData } from "chart.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
|
||||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
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 { Coordinate, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
|
import { EventActions, EventVolume, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-events-volume-chart")
|
@customElement("ak-events-volume-chart")
|
||||||
export class EventVolumeChart extends AKChart<Coordinate[]> {
|
export class EventVolumeChart extends AKChart<EventVolume[]> {
|
||||||
_query?: EventsEventsListRequest;
|
_query?: EventsEventsListRequest;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
@ -24,39 +24,43 @@ export class EventVolumeChart extends AKChart<Coordinate[]> {
|
|||||||
return super.styles.concat(
|
return super.styles.concat(
|
||||||
PFCard,
|
PFCard,
|
||||||
css`
|
css`
|
||||||
.pf-c-card__body {
|
.pf-c-card {
|
||||||
height: 12rem;
|
height: 20rem;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
apiRequest(): Promise<Coordinate[]> {
|
apiRequest(): Promise<EventVolume[]> {
|
||||||
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList(this._query);
|
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList(this._query);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChartData(data: Coordinate[]): ChartData {
|
getChartData(data: EventVolume[]): ChartData {
|
||||||
return {
|
const datasets: ChartData = {
|
||||||
datasets: [
|
datasets: [],
|
||||||
{
|
};
|
||||||
label: msg("Events"),
|
// Get a list of all actions
|
||||||
backgroundColor: "rgba(189, 229, 184, .5)",
|
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.day.getTime(),
|
||||||
|
y: v.count,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
datasets.datasets.push({
|
||||||
|
label: actionToLabel(action as EventActions),
|
||||||
|
backgroundColor: getColorFromString(action),
|
||||||
spanGaps: true,
|
spanGaps: true,
|
||||||
data:
|
data: actionData,
|
||||||
data.map((cord) => {
|
});
|
||||||
return {
|
});
|
||||||
x: cord.xCord || 0,
|
return datasets;
|
||||||
y: cord.yCord || 0,
|
|
||||||
};
|
|
||||||
}) || [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html`<div class="pf-c-card">
|
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 class="pf-c-card__body">${super.render()}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user