From 47ddf0d7f2d81ba6ff8d68aee92e840b05cc4ca2 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 12 Jan 2021 22:26:57 +0100 Subject: [PATCH] web: add UI for notification triggers --- authentik/admin/urls.py | 17 ++++ .../views/events_notifications_triggers.py | 64 +++++++++++++ authentik/events/api/notification_trigger.py | 1 + authentik/events/forms.py | 23 ++++- swagger.yaml | 7 ++ web/src/api/EventTriggers.ts | 25 +++++ .../elements/policies/BoundPoliciesList.ts | 2 +- web/src/interfaces/AdminInterface.ts | 2 +- web/src/pages/events/TransportListPage.ts | 1 + web/src/pages/events/TriggerListPage.ts | 94 +++++++++++++++++++ web/src/routes.ts | 2 + 11 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 authentik/admin/views/events_notifications_triggers.py create mode 100644 web/src/api/EventTriggers.ts create mode 100644 web/src/pages/events/TriggerListPage.ts diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 728957e3c..04bf1c8de 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -5,6 +5,7 @@ from authentik.admin.views import ( applications, certificate_key_pair, events_notifications_transports, + events_notifications_triggers, flows, groups, outposts, @@ -369,4 +370,20 @@ urlpatterns = [ events_notifications_transports.NotificationTransportDeleteView.as_view(), name="notification-transport-delete", ), + # Event Notification Triggers + path( + "events/triggers/create/", + events_notifications_triggers.NotificationTriggerCreateView.as_view(), + name="notification-trigger-create", + ), + path( + "events/triggers//update/", + events_notifications_triggers.NotificationTriggerUpdateView.as_view(), + name="notification-trigger-update", + ), + path( + "events/triggers//delete/", + events_notifications_triggers.NotificationTriggerDeleteView.as_view(), + name="notification-trigger-delete", + ), ] diff --git a/authentik/admin/views/events_notifications_triggers.py b/authentik/admin/views/events_notifications_triggers.py new file mode 100644 index 000000000..9fc7981c0 --- /dev/null +++ b/authentik/admin/views/events_notifications_triggers.py @@ -0,0 +1,64 @@ +"""authentik NotificationTrigger administration""" +from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.mixins import ( + PermissionRequiredMixin as DjangoPermissionRequiredMixin, +) +from django.contrib.messages.views import SuccessMessageMixin +from django.urls import reverse_lazy +from django.utils.translation import gettext as _ +from django.views.generic import UpdateView +from guardian.mixins import PermissionRequiredMixin + +from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView +from authentik.events.forms import NotificationTriggerForm +from authentik.events.models import NotificationTrigger +from authentik.lib.views import CreateAssignPermView + + +class NotificationTriggerCreateView( + SuccessMessageMixin, + BackSuccessUrlMixin, + LoginRequiredMixin, + DjangoPermissionRequiredMixin, + CreateAssignPermView, +): + """Create new NotificationTrigger""" + + model = NotificationTrigger + form_class = NotificationTriggerForm + permission_required = "authentik_events.add_notificationtrigger" + + template_name = "generic/create.html" + success_url = reverse_lazy("authentik_core:shell") + success_message = _("Successfully created Notification Trigger") + + +class NotificationTriggerUpdateView( + SuccessMessageMixin, + BackSuccessUrlMixin, + LoginRequiredMixin, + PermissionRequiredMixin, + UpdateView, +): + """Update application""" + + model = NotificationTrigger + form_class = NotificationTriggerForm + permission_required = "authentik_events.change_notificationtrigger" + + template_name = "generic/update.html" + success_url = reverse_lazy("authentik_core:shell") + success_message = _("Successfully updated Notification Trigger") + + +class NotificationTriggerDeleteView( + LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView +): + """Delete application""" + + model = NotificationTrigger + permission_required = "authentik_events.delete_notificationtrigger" + + template_name = "generic/delete.html" + success_url = reverse_lazy("authentik_core:shell") + success_message = _("Successfully deleted Notification Trigger") diff --git a/authentik/events/api/notification_trigger.py b/authentik/events/api/notification_trigger.py index eec477a54..b059b3ca6 100644 --- a/authentik/events/api/notification_trigger.py +++ b/authentik/events/api/notification_trigger.py @@ -16,6 +16,7 @@ class NotificationTriggerSerializer(ModelSerializer): "name", "transports", "severity", + "group", ] diff --git a/authentik/events/forms.py b/authentik/events/forms.py index 8888b021d..dd2a69a4a 100644 --- a/authentik/events/forms.py +++ b/authentik/events/forms.py @@ -2,7 +2,7 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from authentik.events.models import NotificationTransport +from authentik.events.models import NotificationTransport, NotificationTrigger class NotificationTransportForm(forms.ModelForm): @@ -25,8 +25,23 @@ class NotificationTransportForm(forms.ModelForm): } help_texts = { "webhook_url": _( - ( - "Only required when the Generic or Slack Webhook is used." - ) + ("Only required when the Generic or Slack Webhook is used.") ), } + + +class NotificationTriggerForm(forms.ModelForm): + """NotificationTrigger Form""" + + class Meta: + + model = NotificationTrigger + fields = [ + "name", + "transports", + "severity", + "group", + ] + widgets = { + "name": forms.TextInput(), + } diff --git a/swagger.yaml b/swagger.yaml index e00524546..25ef05c2d 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -7720,6 +7720,13 @@ definitions: - notice - warning - alert + group: + title: Group + description: Define which group of users this notification should be sent + and shown to. If left empty, Notification won't ben sent. + type: string + format: uuid + x-nullable: true Stage: title: Stage obj description: Stage Serializer diff --git a/web/src/api/EventTriggers.ts b/web/src/api/EventTriggers.ts new file mode 100644 index 000000000..f8654ded5 --- /dev/null +++ b/web/src/api/EventTriggers.ts @@ -0,0 +1,25 @@ +import { DefaultClient, QueryArguments, PBResponse } from "./Client"; + +export class Trigger { + pk: string; + name: string; + transports: string[]; + severity: string; + group?: string; + + constructor() { + throw Error(); + } + + static get(pk: string): Promise { + return DefaultClient.fetch(["events", "triggers", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["events", "triggers"], filter); + } + + static adminUrl(rest: string): string { + return `/administration/events/triggers/${rest}`; + } +} diff --git a/web/src/elements/policies/BoundPoliciesList.ts b/web/src/elements/policies/BoundPoliciesList.ts index a9860bcdf..0c9273b0c 100644 --- a/web/src/elements/policies/BoundPoliciesList.ts +++ b/web/src/elements/policies/BoundPoliciesList.ts @@ -44,7 +44,7 @@ export class BoundPoliciesList extends Table { Edit
- +   Delete diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index 1168dda29..9c98b9f25 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -14,7 +14,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ }), new SidebarItem("Events").children( new SidebarItem("Log", "/events/log"), - new SidebarItem("Notification Triggers", "/administration/tasks/"), + new SidebarItem("Notification Triggers", "/events/triggers"), new SidebarItem("Notification Transports", "/events/transports"), ).when((): Promise => { return User.me().then(u => u.is_superuser); diff --git a/web/src/pages/events/TransportListPage.ts b/web/src/pages/events/TransportListPage.ts index 6212411f9..bacfb4287 100644 --- a/web/src/pages/events/TransportListPage.ts +++ b/web/src/pages/events/TransportListPage.ts @@ -3,6 +3,7 @@ import { customElement, html, property, TemplateResult } from "lit-element"; import { DefaultClient, PBResponse } from "../../api/Client"; import { TablePage } from "../../elements/table/TablePage"; +import "../../elements/buttons/ActionButton"; import "../../elements/buttons/ModalButton"; import "../../elements/buttons/SpinnerButton"; import { TableColumn } from "../../elements/table/Table"; diff --git a/web/src/pages/events/TriggerListPage.ts b/web/src/pages/events/TriggerListPage.ts new file mode 100644 index 000000000..046f650b9 --- /dev/null +++ b/web/src/pages/events/TriggerListPage.ts @@ -0,0 +1,94 @@ +import { gettext } from "django"; +import { customElement, html, property, TemplateResult } from "lit-element"; +import { PBResponse } from "../../api/Client"; +import { TablePage } from "../../elements/table/TablePage"; + +import "../../elements/policies/BoundPoliciesList"; +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; +import { TableColumn } from "../../elements/table/Table"; +import { Trigger } from "../../api/EventTriggers"; + +@customElement("ak-event-trigger-list") +export class TriggerListPage extends TablePage { + expandable = true; + + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return gettext("Notification Triggers"); + } + pageDescription(): string { + return gettext("Send notifications on whenever a specific Event is created and matched by policies."); + } + pageIcon(): string { + return gettext("pf-icon pf-icon-attention-bell"); + } + + @property() + order = "name"; + + apiEndpoint(page: number): Promise> { + return Trigger.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Name", "name"), + new TableColumn("Severity", "severity"), + new TableColumn("Sent to group", "group"), + new TableColumn(""), + ]; + } + + row(item: Trigger): TemplateResult[] { + return [ + html`${item.name}`, + html`${item.severity}`, + html`${item.group || gettext("None (trigger disabled)")}`, + html` + + + ${gettext("Edit")} + +
+
  + + + ${gettext("Delete")} + +
+
+ `, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + ${gettext("Create")} + +
+
+ ${super.renderToolbar()} + `; + } + + renderExpanded(item: Trigger): TemplateResult { + return html` + +
+ + +
+ + + `; + } +} diff --git a/web/src/routes.ts b/web/src/routes.ts index 97810f87c..70549305b 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -9,6 +9,7 @@ import "./pages/sources/SourceViewPage"; import "./pages/flows/FlowViewPage"; import "./pages/events/EventListPage"; import "./pages/events/TransportListPage"; +import "./pages/events/TriggerListPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -28,4 +29,5 @@ export const ROUTES: Route[] = [ }), new Route(new RegExp("^/events/log$"), html``), new Route(new RegExp("^/events/transports$"), html``), + new Route(new RegExp("^/events/triggers$"), html``), ];