From 48a40806998745151798bc7075716423fc38bc8f Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 21 Feb 2023 16:17:58 +0100 Subject: [PATCH] add api and webui Signed-off-by: Jens Langhammer --- authentik/interfaces/api.py | 3 +- web/src/admin/AdminInterface.ts | 3 + web/src/admin/Routes.ts | 4 + web/src/admin/interfaces/InterfaceForm.ts | 90 +++++++++++++++ web/src/admin/interfaces/InterfaceListPage.ts | 101 +++++++++++++++++ web/src/admin/tenants/TenantForm.ts | 105 ++++++++++++++++++ 6 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 web/src/admin/interfaces/InterfaceForm.ts create mode 100644 web/src/admin/interfaces/InterfaceListPage.ts diff --git a/authentik/interfaces/api.py b/authentik/interfaces/api.py index 392cd8d31..6dc185e32 100644 --- a/authentik/interfaces/api.py +++ b/authentik/interfaces/api.py @@ -1,6 +1,7 @@ """interfaces API""" from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet +from authentik.core.api.used_by import UsedByMixin from authentik.interfaces.models import Interface @@ -18,7 +19,7 @@ class InterfaceSerializer(ModelSerializer): ] -class InterfaceViewSet(ModelViewSet): +class InterfaceViewSet(UsedByMixin, ModelViewSet): """Interface serializer""" queryset = Interface.objects.all() diff --git a/web/src/admin/AdminInterface.ts b/web/src/admin/AdminInterface.ts index edfe19077..51d8841f7 100644 --- a/web/src/admin/AdminInterface.ts +++ b/web/src/admin/AdminInterface.ts @@ -299,6 +299,9 @@ export class AdminInterface extends Interface { ${t`Tenants`} + + ${t`Interfaces`} + ${t`Certificates`} diff --git a/web/src/admin/Routes.ts b/web/src/admin/Routes.ts index bb695cfa5..ca5857496 100644 --- a/web/src/admin/Routes.ts +++ b/web/src/admin/Routes.ts @@ -132,6 +132,10 @@ export const ROUTES: Route[] = [ await import("@goauthentik/admin/blueprints/BlueprintListPage"); return html``; }), + new Route(new RegExp("^/interfaces$"), async () => { + await import("@goauthentik/admin/interfaces/InterfaceListPage"); + return html``; + }), new Route(new RegExp("^/debug$"), async () => { await import("@goauthentik/admin/DebugPage"); return html``; diff --git a/web/src/admin/interfaces/InterfaceForm.ts b/web/src/admin/interfaces/InterfaceForm.ts new file mode 100644 index 000000000..4cb4343f0 --- /dev/null +++ b/web/src/admin/interfaces/InterfaceForm.ts @@ -0,0 +1,90 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/forms/Radio"; + +import { t } from "@lingui/macro"; + +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { Interface, InterfaceTypeEnum, InterfacesApi } from "@goauthentik/api"; + +@customElement("ak-interface-form") +export class InterfaceForm extends ModelForm { + loadInstance(pk: string): Promise { + return new InterfacesApi(DEFAULT_CONFIG).interfacesRetrieve({ + interfaceUuid: pk, + }); + } + + getSuccessMessage(): string { + if (this.instance) { + return t`Successfully updated interface.`; + } else { + return t`Successfully created interface.`; + } + } + + send = (data: Interface): Promise => { + if (this.instance?.interfaceUuid) { + return new InterfacesApi(DEFAULT_CONFIG).interfacesUpdate({ + interfaceUuid: this.instance.interfaceUuid, + interfaceRequest: data, + }); + } else { + return new InterfacesApi(DEFAULT_CONFIG).interfacesCreate({ + interfaceRequest: data, + }); + } + }; + + renderForm(): TemplateResult { + return html`
+ + +

+ ${t`Name used in the URL when accessing this interface.`} +

+
+ + + +

+ ${t`Configure how authentik will use this interface.`} +

+
+ + +
`; + } +} diff --git a/web/src/admin/interfaces/InterfaceListPage.ts b/web/src/admin/interfaces/InterfaceListPage.ts new file mode 100644 index 000000000..525acd0e5 --- /dev/null +++ b/web/src/admin/interfaces/InterfaceListPage.ts @@ -0,0 +1,101 @@ +import "@goauthentik/admin/interfaces/InterfaceForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@goauthentik/elements/forms/ModalForm"; +import { PaginatedResponse } from "@goauthentik/elements/table/Table"; +import { TableColumn } from "@goauthentik/elements/table/Table"; +import { TablePage } from "@goauthentik/elements/table/TablePage"; + +import { t } from "@lingui/macro"; + +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { Interface, InterfacesApi } from "@goauthentik/api"; + +@customElement("ak-interface-list") +export class InterfaceListPage extends TablePage { + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return t`Interfaces`; + } + pageDescription(): string { + return t`Manage custom interfaces for authentik`; + } + pageIcon(): string { + return "fa fa-home"; + } + + checkbox = true; + + @property() + order = "url_name"; + + async apiEndpoint(page: number): Promise> { + return new InterfacesApi(DEFAULT_CONFIG).interfacesList({ + ordering: this.order, + page: page, + pageSize: (await uiConfig()).pagination.perPage, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [new TableColumn(t`URL Name`, "url_name"), new TableColumn(t`Actions`)]; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return [{ key: t`Domain`, value: item.urlName }]; + }} + .usedBy=${(item: Interface) => { + return new InterfacesApi(DEFAULT_CONFIG).interfacesUsedByList({ + interfaceUuid: item.interfaceUuid, + }); + }} + .delete=${(item: Interface) => { + return new InterfacesApi(DEFAULT_CONFIG).interfacesDestroy({ + interfaceUuid: item.interfaceUuid, + }); + }} + > + + `; + } + + row(item: Interface): TemplateResult[] { + return [ + html`${item.urlName}`, + html` + ${t`Update`} + ${t`Update Interface`} + + + + `, + ]; + } + + renderObjectCreate(): TemplateResult { + return html` + + ${t`Create`} + ${t`Create Interface`} + + + + `; + } +} diff --git a/web/src/admin/tenants/TenantForm.ts b/web/src/admin/tenants/TenantForm.ts index 34b58c685..fb6bdebf6 100644 --- a/web/src/admin/tenants/TenantForm.ts +++ b/web/src/admin/tenants/TenantForm.ts @@ -23,6 +23,10 @@ import { FlowsApi, FlowsInstancesListDesignationEnum, FlowsInstancesListRequest, + Interface, + InterfacesApi, + InterfacesListRequest, + InterfacesListTypeEnum, Tenant, } from "@goauthentik/api"; @@ -368,6 +372,107 @@ export class TenantForm extends ModelForm { + + ${t`Interfaces`} +
+ + => { + const args: InterfacesListRequest = { + ordering: "url_name", + type: InterfacesListTypeEnum.User, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new InterfacesApi( + DEFAULT_CONFIG, + ).interfacesList(args); + return flows.results; + }} + .renderElement=${(iface: Interface): string => { + return iface.urlName; + }} + .renderDescription=${(iface: Interface): TemplateResult => { + return html`${iface.type}`; + }} + .value=${(iface: Interface | undefined): string | undefined => { + return iface?.interfaceUuid; + }} + .selected=${(iface: Interface): boolean => { + return this.instance?.interfaceUser === iface.interfaceUuid; + }} + ?blankable=${true} + > + +

${t`.`}

+
+ + => { + const args: InterfacesListRequest = { + ordering: "url_name", + type: InterfacesListTypeEnum.Flow, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new InterfacesApi( + DEFAULT_CONFIG, + ).interfacesList(args); + return flows.results; + }} + .renderElement=${(iface: Interface): string => { + return iface.urlName; + }} + .renderDescription=${(iface: Interface): TemplateResult => { + return html`${iface.type}`; + }} + .value=${(iface: Interface | undefined): string | undefined => { + return iface?.interfaceUuid; + }} + .selected=${(iface: Interface): boolean => { + return this.instance?.interfaceFlow === iface.interfaceUuid; + }} + ?blankable=${true} + > + +

${t`.`}

+
+ + => { + const args: InterfacesListRequest = { + ordering: "url_name", + type: InterfacesListTypeEnum.Admin, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new InterfacesApi( + DEFAULT_CONFIG, + ).interfacesList(args); + return flows.results; + }} + .renderElement=${(iface: Interface): string => { + return iface.urlName; + }} + .renderDescription=${(iface: Interface): TemplateResult => { + return html`${iface.type}`; + }} + .value=${(iface: Interface | undefined): string | undefined => { + return iface?.interfaceUuid; + }} + .selected=${(iface: Interface): boolean => { + return this.instance?.interfaceAdmin === iface.interfaceUuid; + }} + ?blankable=${true} + > + +

${t`.`}

+
+
+
${t`Other global settings`}