diff --git a/web/src/admin/applications/ApplicationForm.ts b/web/src/admin/applications/ApplicationForm.ts index 3e2df4d3b..b3064e27b 100644 --- a/web/src/admin/applications/ApplicationForm.ts +++ b/web/src/admin/applications/ApplicationForm.ts @@ -1,6 +1,7 @@ import "@goauthentik/admin/providers/ProviderWizard"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; -import { first } from "@goauthentik/common/utils"; +import { first, groupBy } from "@goauthentik/common/utils"; +import "@goauthentik/elements/SearchSelect"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/ModalForm"; @@ -20,6 +21,7 @@ import { CoreApi, PolicyEngineMode, Provider, + ProvidersAllListRequest, ProvidersApi, } from "@goauthentik/api"; @@ -78,29 +80,6 @@ export class ApplicationForm extends ModelForm { return app; }; - groupProviders(providers: Provider[]): TemplateResult { - const m = new Map(); - providers.forEach((p) => { - if (!m.has(p.verboseName || "")) { - m.set(p.verboseName || "", []); - } - const tProviders = m.get(p.verboseName || "") || []; - tProviders.push(p); - }); - return html` - ${Array.from(m).map(([group, providers]) => { - return html` - ${providers.map((p) => { - const selected = this.instance?.provider === p.pk || this.provider === p.pk; - return html``; - })} - `; - })} - `; - } - renderForm(): TemplateResult { return html`
@@ -132,17 +111,32 @@ export class ApplicationForm extends ModelForm {

- + => { + const args: ProvidersAllListRequest = { + ordering: "name", + }; + if (query !== undefined) { + args.search = query; + } + const items = await new ProvidersApi(DEFAULT_CONFIG).providersAllList(args); + return items.results; + }} + .renderElement=${(item: Provider): string => { + return item.name; + }} + .value=${(item: Provider | undefined): number | undefined => { + return item?.pk; + }} + .groupBy=${(items: Provider[]) => { + return groupBy(items, (item) => item.verboseName); + }} + .selected=${(item: Provider): boolean => { + return this.instance?.provider === item.pk; + }} + ?blankable=${true} + > +

${t`Select a provider that this application should use. Alternatively, create a new provider.`}

diff --git a/web/src/admin/applications/ApplicationListPage.ts b/web/src/admin/applications/ApplicationListPage.ts index 203b91720..6e7a8b044 100644 --- a/web/src/admin/applications/ApplicationListPage.ts +++ b/web/src/admin/applications/ApplicationListPage.ts @@ -70,6 +70,9 @@ export class ApplicationListPage extends TablePage { text-align: center; vertical-align: middle; } + .pf-c-sidebar.pf-m-gutter > .pf-c-sidebar__main > * + * { + margin-left: calc(var(--pf-c-sidebar__main--child--MarginLeft) / 2); + } `, ); } diff --git a/web/src/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage.ts b/web/src/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage.ts index 813f9e2be..15ded0e83 100644 --- a/web/src/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage.ts +++ b/web/src/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage.ts @@ -50,7 +50,7 @@ export class TypeOAuthCodeApplicationWizardPage extends WizardFormPage { => { const args: FlowsInstancesListRequest = { - ordering: "name", + ordering: "slug", designation: FlowsInstancesListDesignationEnum.Authorization, }; if (query !== undefined) { diff --git a/web/src/admin/events/TransportForm.ts b/web/src/admin/events/TransportForm.ts index 82a55f0a8..bfc0fcb76 100644 --- a/web/src/admin/events/TransportForm.ts +++ b/web/src/admin/events/TransportForm.ts @@ -1,5 +1,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; +import "@goauthentik/elements/SearchSelect"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; @@ -8,13 +9,14 @@ import { t } from "@lingui/macro"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { until } from "lit/directives/until.js"; import { EventsApi, NotificationTransport, NotificationTransportModeEnum, + NotificationWebhookMapping, PropertymappingsApi, + PropertymappingsNotificationListRequest, } from "@goauthentik/api"; @customElement("ak-event-transport-form") @@ -132,26 +134,33 @@ export class TransportForm extends ModelForm { label=${t`Webhook Mapping`} name="webhookMapping" > - + => { + const args: PropertymappingsNotificationListRequest = { + ordering: "name", + }; + if (query !== undefined) { + args.search = query; + } + const items = await new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsNotificationList(args); + return items.results; + }} + .renderElement=${(item: NotificationWebhookMapping): string => { + return item.name; + }} + .value=${(item: NotificationWebhookMapping | undefined): string | undefined => { + return item?.pk; + }} + .selected=${(item: NotificationWebhookMapping): boolean => { + return this.instance?.webhookMapping === item.pk; + }} + ?blankable=${true} + > +
diff --git a/web/src/admin/outposts/OutpostForm.ts b/web/src/admin/outposts/OutpostForm.ts index 2c85bf1ff..a213bf5bc 100644 --- a/web/src/admin/outposts/OutpostForm.ts +++ b/web/src/admin/outposts/OutpostForm.ts @@ -1,6 +1,8 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { docLink } from "@goauthentik/common/global"; +import { groupBy } from "@goauthentik/common/utils"; import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/SearchSelect"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import YAML from "yaml"; @@ -12,7 +14,14 @@ import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { until } from "lit/directives/until.js"; -import { Outpost, OutpostTypeEnum, OutpostsApi, ProvidersApi } from "@goauthentik/api"; +import { + Outpost, + OutpostTypeEnum, + OutpostsApi, + OutpostsServiceConnectionsAllListRequest, + ProvidersApi, + ServiceConnection, +} from "@goauthentik/api"; @customElement("ak-outpost-form") export class OutpostForm extends ModelForm { @@ -134,32 +143,38 @@ export class OutpostForm extends ModelForm { - + => { + const args: OutpostsServiceConnectionsAllListRequest = { + ordering: "name", + }; + if (query !== undefined) { + args.search = query; + } + const items = await new OutpostsApi( + DEFAULT_CONFIG, + ).outpostsServiceConnectionsAllList(args); + return items.results; + }} + .renderElement=${(item: ServiceConnection): string => { + return item.name; + }} + .value=${(item: ServiceConnection | undefined): string | undefined => { + return item?.pk; + }} + .groupBy=${(items: ServiceConnection[]) => { + return groupBy(items, (item) => item.verboseName); + }} + .selected=${(item: ServiceConnection, items: ServiceConnection[]): boolean => { + let selected = this.instance?.serviceConnection === sc.pk; + if (items.length === 1 && !this.instance) { + selected = true; + } + return selected; + }} + ?blankable=${true} + > +

${t`Selecting an integration enables the management of the outpost by authentik.`}

diff --git a/web/src/admin/outposts/ServiceConnectionDockerForm.ts b/web/src/admin/outposts/ServiceConnectionDockerForm.ts index cfaf32237..8b9c1b3ab 100644 --- a/web/src/admin/outposts/ServiceConnectionDockerForm.ts +++ b/web/src/admin/outposts/ServiceConnectionDockerForm.ts @@ -1,5 +1,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; +import "@goauthentik/elements/SearchSelect"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; @@ -8,9 +9,14 @@ 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 { until } from "lit/directives/until.js"; -import { CryptoApi, DockerServiceConnection, OutpostsApi } from "@goauthentik/api"; +import { + CertificateKeyPair, + CryptoApi, + CryptoCertificatekeypairsListRequest, + DockerServiceConnection, + OutpostsApi, +} from "@goauthentik/api"; @customElement("ak-service-connection-docker-form") export class ServiceConnectionDockerForm extends ModelForm { @@ -79,34 +85,33 @@ export class ServiceConnectionDockerForm extends ModelForm - + => { + const args: CryptoCertificatekeypairsListRequest = { + ordering: "name", + hasKey: true, + includeDetails: false, + }; + if (query !== undefined) { + args.search = query; + } + const certificates = await new CryptoApi( + DEFAULT_CONFIG, + ).cryptoCertificatekeypairsList(args); + return certificates.results; + }} + .renderElement=${(item: CertificateKeyPair): string => { + return item.name; + }} + .value=${(item: CertificateKeyPair | undefined): string | undefined => { + return item?.pk; + }} + .selected=${(item: CertificateKeyPair): boolean => { + return this.instance?.tlsVerification === item.pk; + }} + ?blankable=${true} + > +

${t`CA which the endpoint's Certificate is verified against. Can be left empty for no validation.`}

@@ -115,34 +120,33 @@ export class ServiceConnectionDockerForm extends ModelForm - + => { + const args: CryptoCertificatekeypairsListRequest = { + ordering: "name", + hasKey: true, + includeDetails: false, + }; + if (query !== undefined) { + args.search = query; + } + const certificates = await new CryptoApi( + DEFAULT_CONFIG, + ).cryptoCertificatekeypairsList(args); + return certificates.results; + }} + .renderElement=${(item: CertificateKeyPair): string => { + return item.name; + }} + .value=${(item: CertificateKeyPair | undefined): string | undefined => { + return item?.pk; + }} + .selected=${(item: CertificateKeyPair): boolean => { + return this.instance?.tlsAuthentication === item.pk; + }} + ?blankable=${true} + > +

${t`Certificate/Key used for authentication. Can be left empty for no authentication.`}

diff --git a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts index 57da744f4..0d565067e 100644 --- a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts +++ b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts @@ -85,7 +85,7 @@ export class OAuth2ProviderFormPage extends ModelForm { => { const args: FlowsInstancesListRequest = { - ordering: "name", + ordering: "slug", designation: FlowsInstancesListDesignationEnum.Authorization, }; if (query !== undefined) { @@ -103,6 +103,9 @@ export class OAuth2ProviderFormPage extends ModelForm { .value=${(flow: Flow | undefined): string | undefined => { return flow?.pk; }} + .selected=${(flow: Flow): boolean => { + return flow.pk === this.instance?.authorizationFlow; + }} >

diff --git a/web/src/admin/providers/proxy/ProxyProviderForm.ts b/web/src/admin/providers/proxy/ProxyProviderForm.ts index 8e9c0b449..830b60cc8 100644 --- a/web/src/admin/providers/proxy/ProxyProviderForm.ts +++ b/web/src/admin/providers/proxy/ProxyProviderForm.ts @@ -301,7 +301,7 @@ export class ProxyProviderFormPage extends ModelForm { => { const args: FlowsInstancesListRequest = { - ordering: "name", + ordering: "slug", designation: FlowsInstancesListDesignationEnum.Authorization, }; if (query !== undefined) { diff --git a/web/src/admin/providers/saml/SAMLProviderForm.ts b/web/src/admin/providers/saml/SAMLProviderForm.ts index 8a975486e..05b75e83c 100644 --- a/web/src/admin/providers/saml/SAMLProviderForm.ts +++ b/web/src/admin/providers/saml/SAMLProviderForm.ts @@ -75,7 +75,7 @@ export class SAMLProviderFormPage extends ModelForm { => { const args: FlowsInstancesListRequest = { - ordering: "name", + ordering: "slug", designation: FlowsInstancesListDesignationEnum.Authorization, }; if (query !== undefined) { diff --git a/web/src/admin/providers/saml/SAMLProviderImportForm.ts b/web/src/admin/providers/saml/SAMLProviderImportForm.ts index 2d0760820..d8a948d43 100644 --- a/web/src/admin/providers/saml/SAMLProviderImportForm.ts +++ b/web/src/admin/providers/saml/SAMLProviderImportForm.ts @@ -50,7 +50,7 @@ export class SAMLProviderImportForm extends Form { => { const args: FlowsInstancesListRequest = { - ordering: "name", + ordering: "slug", designation: FlowsInstancesListDesignationEnum.Authorization, }; if (query !== undefined) { diff --git a/web/src/admin/sources/saml/SAMLSourceForm.ts b/web/src/admin/sources/saml/SAMLSourceForm.ts index a8e1ad1a8..cf41a1313 100644 --- a/web/src/admin/sources/saml/SAMLSourceForm.ts +++ b/web/src/admin/sources/saml/SAMLSourceForm.ts @@ -20,8 +20,10 @@ import { CryptoApi, CryptoCertificatekeypairsListRequest, DigestAlgorithmEnum, + Flow, FlowsApi, FlowsInstancesListDesignationEnum, + FlowsInstancesListRequest, NameIdPolicyEnum, SAMLSource, SignatureAlgorithmEnum, @@ -478,42 +480,44 @@ export class SAMLSourceForm extends ModelForm { ?required=${true} name="preAuthenticationFlow" > - + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: + FlowsInstancesListDesignationEnum.StageConfiguration, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + let selected = this.instance?.preAuthenticationFlow === flow.pk; + if ( + !this.instance?.pk && + !this.instance?.preAuthenticationFlow && + flow.slug === "default-source-pre-authentication" + ) { + selected = true; + } + return selected; + }} + ?blankable=${true} + > +

${t`Flow used before authentication.`}

{ ?required=${true} name="authenticationFlow" > - + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: FlowsInstancesListDesignationEnum.Authentication, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + let selected = this.instance?.authenticationFlow === flow.pk; + if ( + !this.instance?.pk && + !this.instance?.authenticationFlow && + flow.slug === "default-source-authentication" + ) { + selected = true; + } + return selected; + }} + ?blankable=${true} + > +

${t`Flow to use when authenticating existing users.`}

@@ -566,41 +571,43 @@ export class SAMLSourceForm extends ModelForm { ?required=${true} name="enrollmentFlow" > - + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: FlowsInstancesListDesignationEnum.Enrollment, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + let selected = this.instance?.enrollmentFlow === flow.pk; + if ( + !this.instance?.pk && + !this.instance?.enrollmentFlow && + flow.slug === "default-source-enrollment" + ) { + selected = true; + } + return selected; + }} + ?blankable=${true} + > +

${t`Flow to use when enrolling new users.`}

diff --git a/web/src/admin/stages/invitation/InvitationForm.ts b/web/src/admin/stages/invitation/InvitationForm.ts index 31a4fa788..045ecf12c 100644 --- a/web/src/admin/stages/invitation/InvitationForm.ts +++ b/web/src/admin/stages/invitation/InvitationForm.ts @@ -78,7 +78,7 @@ export class InvitationForm extends ModelForm { => { const args: FlowsInstancesListRequest = { - ordering: "name", + ordering: "slug", designation: FlowsInstancesListDesignationEnum.Enrollment, }; if (query !== undefined) { diff --git a/web/src/admin/tenants/TenantForm.ts b/web/src/admin/tenants/TenantForm.ts index aebe6e8f7..905b5eae5 100644 --- a/web/src/admin/tenants/TenantForm.ts +++ b/web/src/admin/tenants/TenantForm.ts @@ -12,15 +12,16 @@ import { t } from "@lingui/macro"; import { TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; -import { until } from "lit/directives/until.js"; import { CertificateKeyPair, CoreApi, CryptoApi, CryptoCertificatekeypairsListRequest, + Flow, FlowsApi, FlowsInstancesListDesignationEnum, + FlowsInstancesListRequest, Tenant, } from "@goauthentik/api"; @@ -144,35 +145,35 @@ export class TenantForm extends ModelForm { label=${t`Authentication flow`} name="flowAuthentication" > - + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: FlowsInstancesListDesignationEnum.Authentication, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + return this.instance?.flowAuthentication === flow.pk; + }} + ?blankable=${true} + > +

${t`Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used.`}

@@ -181,64 +182,70 @@ export class TenantForm extends ModelForm { label=${t`Invalidation flow`} name="flowInvalidation" > - + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: FlowsInstancesListDesignationEnum.Invalidation, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + return this.instance?.flowInvalidation === flow.pk; + }} + ?blankable=${true} + > + +

${t`Flow used to logout. If left empty, the first applicable flow sorted by the slug is used.`}

- + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: FlowsInstancesListDesignationEnum.Recovery, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + return this.instance?.flowRecovery === flow.pk; + }} + ?blankable=${true} + > +

${t`Recovery flow. If left empty, the first applicable flow sorted by the slug is used.`}

@@ -247,34 +254,35 @@ export class TenantForm extends ModelForm { label=${t`Unenrollment flow`} name="flowUnenrollment" > - + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: FlowsInstancesListDesignationEnum.Unenrollment, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + return this.instance?.flowUnenrollment === flow.pk; + }} + ?blankable=${true} + > +

${t`If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown.`}

@@ -283,69 +291,71 @@ export class TenantForm extends ModelForm { label=${t`User settings flow`} name="flowUserSettings" > - + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: + FlowsInstancesListDesignationEnum.StageConfiguration, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + return this.instance?.flowUserSettings === flow.pk; + }} + ?blankable=${true} + > +

${t`If set, users are able to configure details of their profile.`}

- + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: + FlowsInstancesListDesignationEnum.StageConfiguration, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList( + args, + ); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return flow.name; + }} + .renderDescription=${(flow: Flow): string => { + return flow.slug; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + return this.instance?.flowDeviceCode === flow.pk; + }} + ?blankable=${true} + > +

${t`If set, the OAuth Device Code profile can be used, and the selected flow will be used to enter the code.`}