web: add UI for notification triggers
This commit is contained in:
		| @ -5,6 +5,7 @@ from authentik.admin.views import ( | |||||||
|     applications, |     applications, | ||||||
|     certificate_key_pair, |     certificate_key_pair, | ||||||
|     events_notifications_transports, |     events_notifications_transports, | ||||||
|  |     events_notifications_triggers, | ||||||
|     flows, |     flows, | ||||||
|     groups, |     groups, | ||||||
|     outposts, |     outposts, | ||||||
| @ -369,4 +370,20 @@ urlpatterns = [ | |||||||
|         events_notifications_transports.NotificationTransportDeleteView.as_view(), |         events_notifications_transports.NotificationTransportDeleteView.as_view(), | ||||||
|         name="notification-transport-delete", |         name="notification-transport-delete", | ||||||
|     ), |     ), | ||||||
|  |     # Event Notification Triggers | ||||||
|  |     path( | ||||||
|  |         "events/triggers/create/", | ||||||
|  |         events_notifications_triggers.NotificationTriggerCreateView.as_view(), | ||||||
|  |         name="notification-trigger-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "events/triggers/<uuid:pk>/update/", | ||||||
|  |         events_notifications_triggers.NotificationTriggerUpdateView.as_view(), | ||||||
|  |         name="notification-trigger-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "events/triggers/<uuid:pk>/delete/", | ||||||
|  |         events_notifications_triggers.NotificationTriggerDeleteView.as_view(), | ||||||
|  |         name="notification-trigger-delete", | ||||||
|  |     ), | ||||||
| ] | ] | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								authentik/admin/views/events_notifications_triggers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								authentik/admin/views/events_notifications_triggers.py
									
									
									
									
									
										Normal file
									
								
							| @ -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") | ||||||
| @ -16,6 +16,7 @@ class NotificationTriggerSerializer(ModelSerializer): | |||||||
|             "name", |             "name", | ||||||
|             "transports", |             "transports", | ||||||
|             "severity", |             "severity", | ||||||
|  |             "group", | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| from django import forms | from django import forms | ||||||
| from django.utils.translation import gettext_lazy as _ | 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): | class NotificationTransportForm(forms.ModelForm): | ||||||
| @ -25,8 +25,23 @@ class NotificationTransportForm(forms.ModelForm): | |||||||
|         } |         } | ||||||
|         help_texts = { |         help_texts = { | ||||||
|             "webhook_url": _( |             "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(), | ||||||
|  |         } | ||||||
|  | |||||||
| @ -7720,6 +7720,13 @@ definitions: | |||||||
|           - notice |           - notice | ||||||
|           - warning |           - warning | ||||||
|           - alert |           - 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: |   Stage: | ||||||
|     title: Stage obj |     title: Stage obj | ||||||
|     description: Stage Serializer |     description: Stage Serializer | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								web/src/api/EventTriggers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								web/src/api/EventTriggers.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<Trigger> { | ||||||
|  |         return DefaultClient.fetch<Trigger>(["events", "triggers", pk]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static list(filter?: QueryArguments): Promise<PBResponse<Trigger>> { | ||||||
|  |         return DefaultClient.fetch<PBResponse<Trigger>>(["events", "triggers"], filter); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static adminUrl(rest: string): string { | ||||||
|  |         return `/administration/events/triggers/${rest}`; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -44,7 +44,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> { | |||||||
|                     Edit |                     Edit | ||||||
|                 </ak-spinner-button> |                 </ak-spinner-button> | ||||||
|                 <div slot="modal"></div> |                 <div slot="modal"></div> | ||||||
|             </ak-modal-button> |             </ak-modal-button>  | ||||||
|             <ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/delete/`)}"> |             <ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/delete/`)}"> | ||||||
|                 <ak-spinner-button slot="trigger" class="pf-m-danger"> |                 <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|                     Delete |                     Delete | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ | |||||||
|     }), |     }), | ||||||
|     new SidebarItem("Events").children( |     new SidebarItem("Events").children( | ||||||
|         new SidebarItem("Log", "/events/log"), |         new SidebarItem("Log", "/events/log"), | ||||||
|         new SidebarItem("Notification Triggers", "/administration/tasks/"), |         new SidebarItem("Notification Triggers", "/events/triggers"), | ||||||
|         new SidebarItem("Notification Transports", "/events/transports"), |         new SidebarItem("Notification Transports", "/events/transports"), | ||||||
|     ).when((): Promise<boolean> => { |     ).when((): Promise<boolean> => { | ||||||
|         return User.me().then(u => u.is_superuser); |         return User.me().then(u => u.is_superuser); | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import { customElement, html, property, TemplateResult } from "lit-element"; | |||||||
| import { DefaultClient, PBResponse } from "../../api/Client"; | import { DefaultClient, PBResponse } from "../../api/Client"; | ||||||
| import { TablePage } from "../../elements/table/TablePage"; | import { TablePage } from "../../elements/table/TablePage"; | ||||||
|  |  | ||||||
|  | import "../../elements/buttons/ActionButton"; | ||||||
| import "../../elements/buttons/ModalButton"; | import "../../elements/buttons/ModalButton"; | ||||||
| import "../../elements/buttons/SpinnerButton"; | import "../../elements/buttons/SpinnerButton"; | ||||||
| import { TableColumn } from "../../elements/table/Table"; | import { TableColumn } from "../../elements/table/Table"; | ||||||
|  | |||||||
							
								
								
									
										94
									
								
								web/src/pages/events/TriggerListPage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								web/src/pages/events/TriggerListPage.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<Trigger> { | ||||||
|  |     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<PBResponse<Trigger>> { | ||||||
|  |         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` | ||||||
|  |             <ak-modal-button href="${Trigger.adminUrl(`${item.pk}/update/`)}"> | ||||||
|  |                 <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                     ${gettext("Edit")} | ||||||
|  |                 </ak-spinner-button> | ||||||
|  |                 <div slot="modal"></div> | ||||||
|  |             </ak-modal-button>  | ||||||
|  |             <ak-modal-button href="${Trigger.adminUrl(`${item.pk}/delete/`)}"> | ||||||
|  |                 <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                     ${gettext("Delete")} | ||||||
|  |                 </ak-spinner-button> | ||||||
|  |                 <div slot="modal"></div> | ||||||
|  |             </ak-modal-button> | ||||||
|  |             `, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     renderToolbar(): TemplateResult { | ||||||
|  |         return html` | ||||||
|  |         <ak-modal-button href=${Trigger.adminUrl("create/")}> | ||||||
|  |             <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                 ${gettext("Create")} | ||||||
|  |             </ak-spinner-button> | ||||||
|  |             <div slot="modal"></div> | ||||||
|  |         </ak-modal-button> | ||||||
|  |         ${super.renderToolbar()} | ||||||
|  |         `; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     renderExpanded(item: Trigger): TemplateResult { | ||||||
|  |         return html` | ||||||
|  |         <td role="cell" colspan="4"> | ||||||
|  |             <div class="pf-c-table__expandable-row-content"> | ||||||
|  |                 <ak-bound-policies-list .target=${item.pk}> | ||||||
|  |                 </ak-bound-policies-list> | ||||||
|  |             </div> | ||||||
|  |         </td> | ||||||
|  |         <td></td> | ||||||
|  |         <td></td>`; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -9,6 +9,7 @@ import "./pages/sources/SourceViewPage"; | |||||||
| import "./pages/flows/FlowViewPage"; | import "./pages/flows/FlowViewPage"; | ||||||
| import "./pages/events/EventListPage"; | import "./pages/events/EventListPage"; | ||||||
| import "./pages/events/TransportListPage"; | import "./pages/events/TransportListPage"; | ||||||
|  | import "./pages/events/TriggerListPage"; | ||||||
|  |  | ||||||
| export const ROUTES: Route[] = [ | export const ROUTES: Route[] = [ | ||||||
|     // Prevent infinite Shell loops |     // Prevent infinite Shell loops | ||||||
| @ -28,4 +29,5 @@ export const ROUTES: Route[] = [ | |||||||
|     }), |     }), | ||||||
|     new Route(new RegExp("^/events/log$"), html`<ak-event-list></ak-event-list>`), |     new Route(new RegExp("^/events/log$"), html`<ak-event-list></ak-event-list>`), | ||||||
|     new Route(new RegExp("^/events/transports$"), html`<ak-event-transport-list></ak-event-transport-list>`), |     new Route(new RegExp("^/events/transports$"), html`<ak-event-transport-list></ak-event-transport-list>`), | ||||||
|  |     new Route(new RegExp("^/events/triggers$"), html`<ak-event-trigger-list></ak-event-trigger-list>`), | ||||||
| ]; | ]; | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer