From 93478a55d7b35909b69cf7421070d96b8cf250eb Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Fri, 19 Feb 2021 19:29:17 +0100 Subject: [PATCH] web: migrate Stage List to web --- .../templates/administration/stage/list.html | 143 ------------------ authentik/admin/urls.py | 1 - authentik/admin/views/stages.py | 28 +--- authentik/flows/api/stages.py | 14 +- web/src/api/Flows.ts | 16 +- web/src/interfaces/AdminInterface.ts | 28 ++-- .../pages/admin-overview/AdminOverviewPage.ts | 2 +- .../pages/applications/ApplicationListPage.ts | 2 +- .../pages/applications/ApplicationViewPage.ts | 2 +- web/src/pages/events/EventInfo.ts | 2 +- web/src/pages/flows/FlowListPage.ts | 2 +- web/src/pages/outposts/OutpostListPage.ts | 2 +- web/src/pages/providers/ProviderListPage.ts | 4 +- .../providers/RelatedApplicationButton.ts | 2 +- web/src/pages/sources/SourcesListPage.ts | 2 +- web/src/pages/stages/StageListPage.ts | 100 ++++++++++++ web/src/routes.ts | 38 ++--- 17 files changed, 172 insertions(+), 216 deletions(-) delete mode 100644 authentik/admin/templates/administration/stage/list.html create mode 100644 web/src/pages/stages/StageListPage.ts diff --git a/authentik/admin/templates/administration/stage/list.html b/authentik/admin/templates/administration/stage/list.html deleted file mode 100644 index d71c30348..000000000 --- a/authentik/admin/templates/administration/stage/list.html +++ /dev/null @@ -1,143 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Stages' %} -

-

{% trans "Stages are single steps of a Flow that a user is guided through." %}

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - - - -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - {% for stage in object_list %} - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Flows' %}
-
-
{{ stage.name }}
- {{ stage|verbose_name }} -
-
-
    - {% for flow in stage.flow_set.all %} -
  • {{ flow.slug }}
  • - {% empty %} -
  • -
  • - {% endfor %} -
-
- - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Stages.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any stages." %} - {% else %} - {% trans 'Currently no stages exist. Click the button below to create one.' %} - {% endif %} -
- - - - -
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 60596e00a..2ac5b256b 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -125,7 +125,6 @@ urlpatterns = [ name="provider-delete", ), # Stages - path("stages/", stages.StageListView.as_view(), name="stages"), path("stages/create/", stages.StageCreateView.as_view(), name="stage-create"), path( "stages//update/", diff --git a/authentik/admin/views/stages.py b/authentik/admin/views/stages.py index 55e7623dd..bb4b190eb 100644 --- a/authentik/admin/views/stages.py +++ b/authentik/admin/views/stages.py @@ -4,38 +4,18 @@ 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 guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from guardian.mixins import PermissionRequiredMixin from authentik.admin.views.utils import ( BackSuccessUrlMixin, DeleteMessageView, InheritanceCreateView, - InheritanceListView, InheritanceUpdateView, - SearchListMixin, - UserPaginateListMixin, ) from authentik.flows.models import Stage -class StageListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - InheritanceListView, -): - """Show list of all stages""" - - model = Stage - template_name = "administration/stage/list.html" - permission_required = "authentik_flows.view_stage" - ordering = "name" - search_fields = ["name"] - - class StageCreateView( SuccessMessageMixin, BackSuccessUrlMixin, @@ -49,7 +29,7 @@ class StageCreateView( template_name = "generic/create.html" permission_required = "authentik_flows.add_stage" - success_url = reverse_lazy("authentik_admin:stages") + success_url = "/" success_message = _("Successfully created Stage") @@ -65,7 +45,7 @@ class StageUpdateView( model = Stage permission_required = "authentik_flows.update_application" template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:stages") + success_url = "/" success_message = _("Successfully updated Stage") @@ -75,5 +55,5 @@ class StageDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage model = Stage template_name = "generic/delete.html" permission_required = "authentik_flows.delete_stage" - success_url = reverse_lazy("authentik_admin:stages") + success_url = "/" success_message = _("Successfully deleted Stage") diff --git a/authentik/flows/api/stages.py b/authentik/flows/api/stages.py index 000294cf7..56ba52937 100644 --- a/authentik/flows/api/stages.py +++ b/authentik/flows/api/stages.py @@ -8,8 +8,8 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.viewsets import ReadOnlyModelViewSet from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer +from authentik.flows.api.flows import FlowSerializer from authentik.flows.models import Stage -from authentik.flows.planner import cache_key from authentik.lib.templatetags.authentik_utils import verbose_name from authentik.lib.utils.reflection import all_subclasses @@ -18,6 +18,7 @@ class StageSerializer(ModelSerializer, MetaNameSerializer): """Stage Serializer""" object_type = SerializerMethodField() + flow_set = FlowSerializer(many=True) def get_object_type(self, obj: Stage) -> str: """Get object type so that we know which API Endpoint to use to get the full object""" @@ -26,13 +27,20 @@ class StageSerializer(ModelSerializer, MetaNameSerializer): class Meta: model = Stage - fields = ["pk", "name", "object_type", "verbose_name", "verbose_name_plural"] + fields = [ + "pk", + "name", + "object_type", + "verbose_name", + "verbose_name_plural", + "flow_set", + ] class StageViewSet(ReadOnlyModelViewSet): """Stage Viewset""" - queryset = Stage.objects.all() + queryset = Stage.objects.all().select_related("flow_set") serializer_class = StageSerializer search_fields = ["name"] filterset_fields = ["name"] diff --git a/web/src/api/Flows.ts b/web/src/api/Flows.ts index 1b1af9825..cbf4909d5 100644 --- a/web/src/api/Flows.ts +++ b/web/src/api/Flows.ts @@ -1,4 +1,4 @@ -import { DefaultClient, AKResponse, QueryArguments } from "./Client"; +import { DefaultClient, AKResponse, QueryArguments, BaseInheritanceModel } from "./Client"; import { TypeCreate } from "./Providers"; export enum FlowDesignation { @@ -49,16 +49,26 @@ export class Flow { } } -export class Stage { +export class Stage implements BaseInheritanceModel { pk: string; name: string; - __type__: string; + object_type: string; verbose_name: string; + verbose_name_plural: string; + flow_set: Flow[]; constructor() { throw Error(); } + static get(slug: string): Promise { + return DefaultClient.fetch(["stages", "all", slug]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["stages", "all"], filter); + } + static getTypes(): Promise { return DefaultClient.fetch(["stages", "all", "types"]); } diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index af881bee1..704a212d3 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -20,37 +20,37 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ return User.me().then(u => u.is_superuser); }), new SidebarItem("Resources").children( - new SidebarItem("Applications", "/applications").activeWhen( - `^/applications/(?${SLUG_REGEX})$` + new SidebarItem("Applications", "/core/applications").activeWhen( + `^/core/applications/(?${SLUG_REGEX})$` ), - new SidebarItem("Sources", "/sources").activeWhen( - `^/sources/(?${SLUG_REGEX})$`, + new SidebarItem("Sources", "/core/sources").activeWhen( + `^/core/sources/(?${SLUG_REGEX})$`, ), - new SidebarItem("Providers", "/providers"), - new SidebarItem("Outposts", "/outposts"), - new SidebarItem("Outpost Service Connections", "/outpost-service-connections"), + new SidebarItem("Providers", "/core/providers"), + new SidebarItem("Outposts", "/outpost/outposts"), + new SidebarItem("Outpost Service Connections", "/outpost/service-connections"), ).when((): Promise => { return User.me().then(u => u.is_superuser); }), new SidebarItem("Customisation").children( - new SidebarItem("Policies", "/policies"), - new SidebarItem("Property Mappings", "/property-mappings"), + new SidebarItem("Policies", "/policy/policies"), + new SidebarItem("Property Mappings", "/core/property-mappings"), ).when((): Promise => { return User.me().then(u => u.is_superuser); }), new SidebarItem("Flows").children( - new SidebarItem("Flows", "/flows").activeWhen(`^/flows/(?${SLUG_REGEX})$`), - new SidebarItem("Stages", "/administration/stages/"), + new SidebarItem("Flows", "/flow/flows").activeWhen(`^/flow/flows/(?${SLUG_REGEX})$`), + new SidebarItem("Stages", "/flow/stages"), new SidebarItem("Prompts", "/administration/stages_prompts/"), new SidebarItem("Invitations", "/administration/stages/invitations/"), ).when((): Promise => { return User.me().then(u => u.is_superuser); }), new SidebarItem("Identity & Cryptography").children( - new SidebarItem("User", "/users"), - new SidebarItem("Groups", "/groups"), + new SidebarItem("User", "/identity/users"), + new SidebarItem("Groups", "/identity/groups"), new SidebarItem("Certificates", "/crypto/certificates"), - new SidebarItem("Tokens", "/tokens"), + new SidebarItem("Tokens", "/core/tokens"), ).when((): Promise => { return User.me().then(u => u.is_superuser); }), diff --git a/web/src/pages/admin-overview/AdminOverviewPage.ts b/web/src/pages/admin-overview/AdminOverviewPage.ts index b825f8d0e..00292009a 100644 --- a/web/src/pages/admin-overview/AdminOverviewPage.ts +++ b/web/src/pages/admin-overview/AdminOverviewPage.ts @@ -36,7 +36,7 @@ export class AdminOverviewPage extends LitElement { - + diff --git a/web/src/pages/applications/ApplicationListPage.ts b/web/src/pages/applications/ApplicationListPage.ts index 29738d8ec..608670c58 100644 --- a/web/src/pages/applications/ApplicationListPage.ts +++ b/web/src/pages/applications/ApplicationListPage.ts @@ -50,7 +50,7 @@ export class ApplicationListPage extends TablePage { item.meta_icon ? html`${gettext(` : html``, - html` + html`
${item.name}
diff --git a/web/src/pages/applications/ApplicationViewPage.ts b/web/src/pages/applications/ApplicationViewPage.ts index 41065bc41..3b38bae6a 100644 --- a/web/src/pages/applications/ApplicationViewPage.ts +++ b/web/src/pages/applications/ApplicationViewPage.ts @@ -80,7 +80,7 @@ export class ApplicationViewPage extends LitElement {
diff --git a/web/src/pages/events/EventInfo.ts b/web/src/pages/events/EventInfo.ts index df8c384e8..50a2c12f0 100644 --- a/web/src/pages/events/EventInfo.ts +++ b/web/src/pages/events/EventInfo.ts @@ -107,7 +107,7 @@ export class EventInfo extends LitElement { ${until(Flow.list({ flow_uuid: this.event.context.flow as string, }).then(resp => { - return html`${resp.results[0].name}`; + return html`${resp.results[0].name}`; }), html``)} diff --git a/web/src/pages/flows/FlowListPage.ts b/web/src/pages/flows/FlowListPage.ts index 9689ccd3d..b7ae5a90f 100644 --- a/web/src/pages/flows/FlowListPage.ts +++ b/web/src/pages/flows/FlowListPage.ts @@ -47,7 +47,7 @@ export class FlowListPage extends TablePage { row(item: Flow): TemplateResult[] { return [ - html` + html` ${item.slug} `, html`${item.name}`, diff --git a/web/src/pages/outposts/OutpostListPage.ts b/web/src/pages/outposts/OutpostListPage.ts index 71b636028..81e15e027 100644 --- a/web/src/pages/outposts/OutpostListPage.ts +++ b/web/src/pages/outposts/OutpostListPage.ts @@ -48,7 +48,7 @@ export class OutpostListPage extends TablePage { return [ html`${item.name}`, html``, html``, html` diff --git a/web/src/pages/providers/ProviderListPage.ts b/web/src/pages/providers/ProviderListPage.ts index d0bc61e6f..fe6ed3bab 100644 --- a/web/src/pages/providers/ProviderListPage.ts +++ b/web/src/pages/providers/ProviderListPage.ts @@ -47,13 +47,13 @@ export class ProviderListPage extends TablePage { row(item: Provider): TemplateResult[] { return [ - html` + html` ${item.name} `, item.assigned_application_name ? html` ${gettext("Assigned to application ")} - ${item.assigned_application_name}` : + ${item.assigned_application_name}` : html` ${gettext("Warning: Provider not assigned to any application.")}`, html`${item.verbose_name}`, diff --git a/web/src/pages/providers/RelatedApplicationButton.ts b/web/src/pages/providers/RelatedApplicationButton.ts index 5abe601a3..e7194c8a2 100644 --- a/web/src/pages/providers/RelatedApplicationButton.ts +++ b/web/src/pages/providers/RelatedApplicationButton.ts @@ -14,7 +14,7 @@ export class RelatedApplicationButton extends LitElement { render(): TemplateResult { if (this.provider?.assigned_application_slug) { - return html` + return html` ${this.provider.assigned_application_name} `; } diff --git a/web/src/pages/sources/SourcesListPage.ts b/web/src/pages/sources/SourcesListPage.ts index 30511aa4a..76e9b9410 100644 --- a/web/src/pages/sources/SourcesListPage.ts +++ b/web/src/pages/sources/SourcesListPage.ts @@ -46,7 +46,7 @@ export class SourceListPage extends TablePage { row(item: Source): TemplateResult[] { return [ - html` + html`
${item.name}
${item.enabled ? html`` : html`${gettext("Disabled")}`}
`, diff --git a/web/src/pages/stages/StageListPage.ts b/web/src/pages/stages/StageListPage.ts new file mode 100644 index 000000000..710512a1d --- /dev/null +++ b/web/src/pages/stages/StageListPage.ts @@ -0,0 +1,100 @@ +import { gettext } from "django"; +import { customElement, html, property, TemplateResult } from "lit-element"; +import { AKResponse } from "../../api/Client"; +import { TableColumn } from "../../elements/table/Table"; +import { TablePage } from "../../elements/table/TablePage"; + +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; +import "../../elements/buttons/Dropdown"; +import { until } from "lit-html/directives/until"; +import { Stage } from "../../api/Flows"; + +@customElement("ak-stage-list") +export class StageListPage extends TablePage { + pageTitle(): string { + return "Stages"; + } + pageDescription(): string | undefined { + return "Stages are single steps of a Flow that a user is guided through."; + } + pageIcon(): string { + return "pf-icon pf-icon-plugged"; + } + searchEnabled(): boolean { + return true; + } + + @property() + order = "name"; + + apiEndpoint(page: number): Promise> { + return Stage.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Name", "name"), + new TableColumn("Flows"), + new TableColumn(""), + ]; + } + + row(item: Stage): TemplateResult[] { + return [ + html`
+
${item.name}
+ ${item.verbose_name} +
`, + html`${item.flow_set.map((flow) => { + return html` + ${flow.slug} + `; + })}`, + html` + + + ${gettext("Edit")} + +
+
+ + + ${gettext("Delete")} + +
+
+ `, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + + + ${super.renderToolbar()}`; + } + +} diff --git a/web/src/routes.ts b/web/src/routes.ts index 32f249ce2..c33ce2f76 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -11,6 +11,7 @@ import "./pages/events/RuleListPage"; import "./pages/events/TransportListPage"; import "./pages/flows/FlowListPage"; import "./pages/flows/FlowViewPage"; +import "./pages/groups/GroupListPage"; import "./pages/LibraryPage"; import "./pages/outposts/OutpostListPage"; import "./pages/outposts/OutpostServiceConnectionListPage"; @@ -20,10 +21,10 @@ import "./pages/providers/ProviderListPage"; import "./pages/providers/ProviderViewPage"; import "./pages/sources/SourcesListPage"; import "./pages/sources/SourceViewPage"; -import "./pages/groups/GroupListPage"; -import "./pages/users/UserListPage"; -import "./pages/tokens/TokenListPage"; +import "./pages/stages/StageListPage"; import "./pages/system-tasks/SystemTaskListPage"; +import "./pages/tokens/TokenListPage"; +import "./pages/users/UserListPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -32,24 +33,25 @@ export const ROUTES: Route[] = [ new Route(new RegExp("^/library$"), html``), new Route(new RegExp("^/administration/overview$"), html``), new Route(new RegExp("^/administration/system-tasks$"), html``), - new Route(new RegExp("^/providers$"), html``), - new Route(new RegExp(`^/providers/(?${ID_REGEX})$`)).then((args) => { + new Route(new RegExp("^/core/providers$"), html``), + new Route(new RegExp(`^/core/providers/(?${ID_REGEX})$`)).then((args) => { return html``; }), - new Route(new RegExp("^/applications$"), html``), - new Route(new RegExp(`^/applications/(?${SLUG_REGEX})$`)).then((args) => { + new Route(new RegExp("^/core/applications$"), html``), + new Route(new RegExp(`^/core/applications/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), - new Route(new RegExp("^/sources$"), html``), - new Route(new RegExp(`^/sources/(?${SLUG_REGEX})$`)).then((args) => { + new Route(new RegExp("^/core/sources$"), html``), + new Route(new RegExp(`^/core/sources/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), - new Route(new RegExp("^/policies$"), html``), - new Route(new RegExp("^/groups$"), html``), - new Route(new RegExp("^/users$"), html``), - new Route(new RegExp("^/flows$"), html``), - new Route(new RegExp("^/tokens$"), html``), - new Route(new RegExp(`^/flows/(?${SLUG_REGEX})$`)).then((args) => { + new Route(new RegExp("^/policy/policies$"), html``), + new Route(new RegExp("^/identity/groups$"), html``), + new Route(new RegExp("^/identity/users$"), html``), + new Route(new RegExp("^/core/tokens$"), html``), + new Route(new RegExp("^/flow/stages$"), html``), + new Route(new RegExp("^/flow/flows$"), html``), + new Route(new RegExp(`^/flow/flows/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), new Route(new RegExp("^/events/log$"), html``), @@ -58,8 +60,8 @@ export const ROUTES: Route[] = [ }), new Route(new RegExp("^/events/transports$"), html``), new Route(new RegExp("^/events/rules$"), html``), - new Route(new RegExp("^/property-mappings$"), html``), - new Route(new RegExp("^/outposts$"), html``), - new Route(new RegExp("^/outpost-service-connections$"), html``), + new Route(new RegExp("^/core/property-mappings$"), html``), + new Route(new RegExp("^/outpost/outposts$"), html``), + new Route(new RegExp("^/outpost/service-connections$"), html``), new Route(new RegExp("^/crypto/certificates$"), html``), ];