web/elements: only render form once instance is loaded (#5049)

* web/elements: only render form once instance is loaded

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use radio for transport

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* only wait for instance to be loaded if set

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add hook to load additional data in form

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make send an abstract function instead of attribute

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* ensure form is updated after data is loaded

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove until for select and multi-selects in forms

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't use until for file uploads

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove last until from form

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove deprecated import

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* prevent form double load, add error handling for PreventFormSubmit

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix double creation of inner element in proxy form

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make PreventFormSubmit work correctly

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2023-03-23 14:05:14 +01:00
committed by GitHub
parent 20522558fe
commit 14f0034a0a
29 changed files with 900 additions and 995 deletions

View File

@ -1,5 +1,6 @@
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first, groupBy } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/ModalForm";
@ -13,7 +14,6 @@ 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 {
Application,
@ -195,70 +195,58 @@ export class ApplicationForm extends ModelForm<Application, string> {
${t`If checked, the launch URL will open in a new browser tab or window from the user's application library.`}
</p>
</ak-form-element-horizontal>
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal
label=${t`Icon`}
name="metaIcon"
>
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.metaIcon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`}
${this.instance?.metaIcon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.metaIcon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target =
ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
<span class="pf-c-switch__label">
${t`Clear icon`}
</span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal
label=${t`Icon`}
name="metaIcon"
>
<input
type="text"
value="${first(this.instance?.metaIcon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
</p>
</ak-form-element-horizontal>`;
}),
)}
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
? html`<ak-form-element-horizontal label=${t`Icon`} name="metaIcon">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.metaIcon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.metaIcon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.metaIcon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target =
ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
<span class="pf-c-switch__label">
${t`Clear icon`}
</span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input
type="text"
value="${first(this.instance?.metaIcon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
</p>
</ak-form-element-horizontal>`}
<ak-form-element-horizontal label=${t`Publisher`} name="metaPublisher">
<input
type="text"

View File

@ -9,7 +9,6 @@ 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 {
CoreApi,
@ -17,17 +16,26 @@ import {
EventsApi,
Group,
NotificationRule,
PaginatedNotificationTransportList,
SeverityEnum,
} from "@goauthentik/api";
@customElement("ak-event-rule-form")
export class RuleForm extends ModelForm<NotificationRule, string> {
eventTransports?: PaginatedNotificationTransportList;
loadInstance(pk: string): Promise<NotificationRule> {
return new EventsApi(DEFAULT_CONFIG).eventsRulesRetrieve({
pbmUuid: pk,
});
}
async load(): Promise<void> {
this.eventTransports = await new EventsApi(DEFAULT_CONFIG).eventsTransportsList({
ordering: "name",
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated rule.`;
@ -86,28 +94,14 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Transports`} ?required=${true} name="transports">
<select class="pf-c-form-control" multiple>
${until(
new EventsApi(DEFAULT_CONFIG)
.eventsTransportsList({
ordering: "name",
})
.then((transports) => {
return transports.results.map((transport) => {
const selected = Array.from(
this.instance?.transports || [],
).some((su) => {
return su == transport.pk;
});
return html`<option
value=${ifDefined(transport.pk)}
?selected=${selected}
>
${transport.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.eventTransports?.results.map((transport) => {
const selected = Array.from(this.instance?.transports || []).some((su) => {
return su == transport.pk;
});
return html`<option value=${ifDefined(transport.pk)} ?selected=${selected}>
${transport.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Select which transports should be used to notify the user. If none are selected, the notification will only be shown in the authentik UI.`}
@ -120,7 +114,7 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
<ak-radio
.options=${[
{
label: "Alert",
label: t`Alert`,
value: SeverityEnum.Alert,
default: true,
},

View File

@ -2,6 +2,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
@ -56,35 +57,6 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
}
};
renderTransportModes(): TemplateResult {
return html`
<option
value=${NotificationTransportModeEnum.Local}
?selected=${this.instance?.mode === NotificationTransportModeEnum.Local}
>
${t`Local (notifications will be created within authentik)`}
</option>
<option
value=${NotificationTransportModeEnum.Email}
?selected=${this.instance?.mode === NotificationTransportModeEnum.Email}
>
${t`Email`}
</option>
<option
value=${NotificationTransportModeEnum.Webhook}
?selected=${this.instance?.mode === NotificationTransportModeEnum.Webhook}
>
${t`Webhook (generic)`}
</option>
<option
value=${NotificationTransportModeEnum.WebhookSlack}
?selected=${this.instance?.mode === NotificationTransportModeEnum.WebhookSlack}
>
${t`Webhook (Slack/Discord)`}
</option>
`;
}
onModeChange(mode: string | undefined): void {
if (
mode === NotificationTransportModeEnum.Webhook ||
@ -107,15 +79,32 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Mode`} ?required=${true} name="mode">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value;
this.onModeChange(current);
<ak-radio
@change=${(ev: CustomEvent<NotificationTransportModeEnum>) => {
this.onModeChange(ev.detail);
}}
.options=${[
{
label: t`Local (notifications will be created within authentik)`,
value: NotificationTransportModeEnum.Local,
default: true,
},
{
label: t`Email`,
value: NotificationTransportModeEnum.Email,
},
{
label: t`Webhook (generic)`,
value: NotificationTransportModeEnum.Webhook,
},
{
label: t`Webhook (Slack/Discord)`,
value: NotificationTransportModeEnum.WebhookSlack,
},
]}
.value=${this.instance?.mode}
>
${this.renderTransportModes()}
</select>
</ak-radio>
</ak-form-element-horizontal>
<ak-form-element-horizontal
?hidden=${!this.showWebhook}

View File

@ -2,6 +2,7 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/util
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
@ -12,7 +13,6 @@ 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 {
CapabilitiesEnum,
@ -315,73 +315,62 @@ export class FlowForm extends ModelForm<Flow, string> {
</option>
</select>
</ak-form-element-horizontal>
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal
label=${t`Background`}
name="background"
>
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.background
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`}
${this.instance?.background}
</p>
`
: html``}
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>
${this.instance?.background
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target =
ev.target as HTMLInputElement;
this.clearBackground = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
<span class="pf-c-switch__label">
${t`Clear icon`}
</span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set background image.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal
label=${t`Background`}
name="background"
>
<input
type="text"
value="${first(this.instance?.background, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>`;
}),
)}
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
? html`<ak-form-element-horizontal label=${t`Background`} name="background">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.background
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.background}
</p>
`
: html``}
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>
${this.instance?.background
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target =
ev.target as HTMLInputElement;
this.clearBackground = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
<span class="pf-c-switch__label">
${t`Clear background`}
</span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set background image.`}
</p>
</ak-form-element-horizontal>
`
: html``}`
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input
type="text"
value="${first(this.instance?.background, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Background shown during execution.`}
</p>
</ak-form-element-horizontal>`}
</div>
</ak-form-group>
</form>`;

View File

@ -1,3 +1,4 @@
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first, groupBy } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/HorizontalFormElement";
@ -10,11 +11,13 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
Flow,
FlowStageBinding,
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
InvalidResponseActionEnum,
PolicyEngineMode,
Stage,
@ -85,23 +88,32 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
`;
}
return html`<ak-form-element-horizontal label=${t`Target`} ?required=${true} name="target">
<select class="pf-c-form-control">
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "slug",
})
.then((flows) => {
return flows.results.map((flow) => {
// No ?selected check here, as this input isn't shown on update forms
return html`<option value=${ifDefined(flow.pk)}>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: FlowsInstancesListDesignationEnum.Authorization,
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
return flows.results;
}}
.renderElement=${(flow: Flow): string => {
return RenderFlowOption(flow);
}}
.renderDescription=${(flow: Flow): TemplateResult => {
return html`${flow.name}`;
}}
.value=${(flow: Flow | undefined): string | undefined => {
return flow?.pk;
}}
.selected=${(flow: Flow): boolean => {
return flow.pk === this.instance?.target;
}}
>
</ak-search-select>
</ak-form-element-horizontal>`;
}

View File

@ -10,15 +10,18 @@ import YAML from "yaml";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
Outpost,
OutpostDefaultConfig,
OutpostTypeEnum,
OutpostsApi,
OutpostsServiceConnectionsAllListRequest,
PaginatedLDAPProviderList,
PaginatedProxyProviderList,
PaginatedRadiusProviderList,
ProvidersApi,
ServiceConnection,
} from "@goauthentik/api";
@ -31,6 +34,14 @@ export class OutpostForm extends ModelForm<Outpost, string> {
@property({ type: Boolean })
embedded = false;
@state()
providers?:
| PaginatedProxyProviderList
| PaginatedLDAPProviderList
| PaginatedRadiusProviderList;
defaultConfig?: OutpostDefaultConfig;
async loadInstance(pk: string): Promise<Outpost> {
const o = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesRetrieve({
uuid: pk,
@ -39,6 +50,34 @@ export class OutpostForm extends ModelForm<Outpost, string> {
return o;
}
async load(): Promise<void> {
this.defaultConfig = await new OutpostsApi(
DEFAULT_CONFIG,
).outpostsInstancesDefaultSettingsRetrieve();
switch (this.type) {
case OutpostTypeEnum.Proxy:
this.providers = await new ProvidersApi(DEFAULT_CONFIG).providersProxyList({
ordering: "name",
applicationIsnull: false,
});
break;
case OutpostTypeEnum.Ldap:
this.providers = await new ProvidersApi(DEFAULT_CONFIG).providersLdapList({
ordering: "name",
applicationIsnull: false,
});
break;
case OutpostTypeEnum.Radius:
this.providers = await new ProvidersApi(DEFAULT_CONFIG).providersRadiusList({
ordering: "name",
applicationIsnull: false,
});
break;
case OutpostTypeEnum.UnknownDefaultOpenApi:
this.providers = undefined;
}
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated outpost.`;
@ -60,78 +99,6 @@ export class OutpostForm extends ModelForm<Outpost, string> {
}
};
renderProviders(): Promise<TemplateResult[]> {
switch (this.type) {
case OutpostTypeEnum.Proxy:
return new ProvidersApi(DEFAULT_CONFIG)
.providersProxyList({
ordering: "name",
applicationIsnull: false,
})
.then((providers) => {
return providers.results.map((provider) => {
const selected = Array.from(this.instance?.providers || []).some(
(sp) => {
return sp == provider.pk;
},
);
return html`<option
value=${ifDefined(provider.pk)}
?selected=${selected}
>
${provider.assignedApplicationName} (${provider.externalHost})
</option>`;
});
});
case OutpostTypeEnum.Ldap:
return new ProvidersApi(DEFAULT_CONFIG)
.providersLdapList({
ordering: "name",
applicationIsnull: false,
})
.then((providers) => {
return providers.results.map((provider) => {
const selected = Array.from(this.instance?.providers || []).some(
(sp) => {
return sp == provider.pk;
},
);
return html`<option
value=${ifDefined(provider.pk)}
?selected=${selected}
>
${provider.assignedApplicationName} (${provider.name})
</option>`;
});
});
case OutpostTypeEnum.Radius:
return new ProvidersApi(DEFAULT_CONFIG)
.providersRadiusList({
ordering: "name",
applicationIsnull: false,
})
.then((providers) => {
return providers.results.map((provider) => {
const selected = Array.from(this.instance?.providers || []).some(
(sp) => {
return sp == provider.pk;
},
);
return html`<option
value=${ifDefined(provider.pk)}
?selected=${selected}
>
${provider.assignedApplicationName} (${provider.name})
</option>`;
});
});
case OutpostTypeEnum.UnknownDefaultOpenApi:
return Promise.resolve([
html` <option value="">${t`Unknown outpost type`}</option>`,
]);
}
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
@ -148,6 +115,7 @@ export class OutpostForm extends ModelForm<Outpost, string> {
@change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
this.type = target.selectedOptions[0].value as OutpostTypeEnum;
this.load();
}}
>
<option
@ -162,6 +130,12 @@ export class OutpostForm extends ModelForm<Outpost, string> {
>
${t`LDAP`}
</option>
<option
value=${OutpostTypeEnum.Radius}
?selected=${this.instance?.type === OutpostTypeEnum.Radius}
>
${t`Radius`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Integration`} name="serviceConnection">
@ -213,7 +187,14 @@ export class OutpostForm extends ModelForm<Outpost, string> {
name="providers"
>
<select class="pf-c-form-control" multiple>
${until(this.renderProviders(), html`<option>${t`Loading...`}</option>`)}
${this.providers?.results.map((provider) => {
const selected = Array.from(this.instance?.providers || []).some((sp) => {
return sp == provider.pk;
});
return html`<option value=${ifDefined(provider.pk)} ?selected=${selected}>
${provider.assignedApplicationName} (${provider.name})
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`You can only select providers that match the type of the outpost.`}
@ -223,19 +204,10 @@ export class OutpostForm extends ModelForm<Outpost, string> {
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Configuration`} name="config">
<!-- @ts-ignore -->
<ak-codemirror
mode="yaml"
value="${until(
new OutpostsApi(DEFAULT_CONFIG)
.outpostsInstancesDefaultSettingsRetrieve()
.then((config) => {
let fc = config.config;
if (this.instance) {
fc = this.instance.config;
}
return YAML.stringify(fc);
}),
value="${YAML.stringify(
this.instance ? this.instance.config : this.defaultConfig?.config,
)}"
></ak-codemirror>
<p class="pf-c-form__helper-text">

View File

@ -10,9 +10,15 @@ 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 { AdminApi, EventMatcherPolicy, EventsApi, PoliciesApi, TypeCreate } from "@goauthentik/api";
import {
AdminApi,
App,
EventMatcherPolicy,
EventsApi,
PoliciesApi,
TypeCreate,
} from "@goauthentik/api";
@customElement("ak-policy-event-matcher-form")
export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string> {
@ -22,6 +28,12 @@ export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string
});
}
async load(): Promise<void> {
this.apps = await new AdminApi(DEFAULT_CONFIG).adminAppsList();
}
apps?: App[];
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated policy.`;
@ -118,19 +130,14 @@ export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string
<option value="" ?selected=${this.instance?.app === undefined}>
---------
</option>
${until(
new AdminApi(DEFAULT_CONFIG).adminAppsList().then((apps) => {
return apps.map((app) => {
return html`<option
value=${app.name}
?selected=${this.instance?.app === app.name}
>
${app.label}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.apps?.map((app) => {
return html`<option
value=${app.name}
?selected=${this.instance?.app === app.name}
>
${app.label}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Match events created by selected application. When left empty, all applications are matched.`}

View File

@ -11,9 +11,8 @@ import "@goauthentik/elements/utils/TimeDeltaHelp";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
CertificateKeyPair,
@ -26,6 +25,8 @@ import {
FlowsInstancesListRequest,
IssuerModeEnum,
OAuth2Provider,
PaginatedOAuthSourceList,
PaginatedScopeMappingList,
PropertymappingsApi,
ProvidersApi,
SourcesApi,
@ -34,19 +35,31 @@ import {
@customElement("ak-provider-oauth2-form")
export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
loadInstance(pk: number): Promise<OAuth2Provider> {
return new ProvidersApi(DEFAULT_CONFIG)
.providersOauth2Retrieve({
id: pk,
})
.then((provider) => {
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
return provider;
});
propertyMappings?: PaginatedScopeMappingList;
oauthSources?: PaginatedOAuthSourceList;
@state()
showClientSecret = true;
async loadInstance(pk: number): Promise<OAuth2Provider> {
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Retrieve({
id: pk,
});
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
return provider;
}
@property({ type: Boolean })
showClientSecret = true;
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsScopeList({
ordering: "scope_name",
});
this.oauthSources = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthList({
ordering: "name",
hasJwks: true,
});
}
getSuccessMessage(): string {
if (this.instance) {
@ -287,36 +300,27 @@ ${this.instance?.redirectUris}</textarea
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
<select class="pf-c-form-control" multiple>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({
ordering: "scope_name",
})
.then((scopes) => {
return scopes.results.map((scope) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
scope.managed?.startsWith(
"goauthentik.io/providers/oauth2/scope-",
) || false;
} else {
selected = Array.from(
this.instance?.propertyMappings,
).some((su) => {
return su == scope.pk;
});
}
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.propertyMappings?.results.map((scope) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
scope.managed?.startsWith(
"goauthentik.io/providers/oauth2/scope-",
) || false;
} else {
selected = Array.from(this.instance?.propertyMappings).some(
(su) => {
return su == scope.pk;
},
);
}
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Select which scopes can be used by the client. The client still has to specify the scope to access the data.`}
@ -413,29 +417,14 @@ ${this.instance?.redirectUris}</textarea
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Trusted OIDC Sources`} name="jwksSources">
<select class="pf-c-form-control" multiple>
${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthList({
ordering: "name",
hasJwks: true,
})
.then((sources) => {
return sources.results.map((source) => {
const selected = (
this.instance?.jwksSources || []
).some((su) => {
return su == source.pk;
});
return html`<option
value=${source.pk}
?selected=${selected}
>
${source.name} (${source.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.oauthSources?.results.map((source) => {
const selected = (this.instance?.jwksSources || []).some((su) => {
return su == source.pk;
});
return html`<option value=${source.pk} ?selected=${selected}>
${source.name} (${source.slug})
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.`}

View File

@ -13,7 +13,6 @@ import { CSSResult, css } from "lit";
import { TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFList from "@patternfly/patternfly/components/List/list.css";
@ -28,6 +27,8 @@ import {
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
PaginatedOAuthSourceList,
PaginatedScopeMappingList,
PropertymappingsApi,
ProvidersApi,
ProxyMode,
@ -51,18 +52,30 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
);
}
loadInstance(pk: number): Promise<ProxyProvider> {
return new ProvidersApi(DEFAULT_CONFIG)
.providersProxyRetrieve({
id: pk,
})
.then((provider) => {
this.showHttpBasic = first(provider.basicAuthEnabled, true);
this.mode = first(provider.mode, ProxyMode.Proxy);
return provider;
});
async loadInstance(pk: number): Promise<ProxyProvider> {
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersProxyRetrieve({
id: pk,
});
this.showHttpBasic = first(provider.basicAuthEnabled, true);
this.mode = first(provider.mode, ProxyMode.Proxy);
return provider;
}
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsScopeList({
ordering: "scope_name",
});
this.oauthSources = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthList({
ordering: "name",
hasJwks: true,
});
}
propertyMappings?: PaginatedScopeMappingList;
oauthSources?: PaginatedOAuthSourceList;
@state()
showHttpBasic = true;
@ -392,34 +405,23 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({
ordering: "scope_name",
})
.then((scopes) => {
return scopes.results
.filter((scope) => {
return !scope.managed?.startsWith(
"goauthentik.io/providers",
);
})
.map((scope) => {
const selected = (
this.instance?.propertyMappings || []
).some((su) => {
return su == scope.pk;
});
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.propertyMappings?.results
.filter((scope) => {
return !scope.managed?.startsWith("goauthentik.io/providers");
})
.map((scope) => {
const selected = (this.instance?.propertyMappings || []).some(
(su) => {
return su == scope.pk;
},
);
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Additional scope mappings, which are passed to the proxy.`}
@ -497,29 +499,14 @@ ${this.instance?.skipPathRegex}</textarea
${this.showHttpBasic ? this.renderHttpBasic() : html``}
<ak-form-element-horizontal label=${t`Trusted OIDC Sources`} name="jwksSources">
<select class="pf-c-form-control" multiple>
${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthList({
ordering: "name",
hasJwks: true,
})
.then((sources) => {
return sources.results.map((source) => {
const selected = (
this.instance?.jwksSources || []
).some((su) => {
return su == source.pk;
});
return html`<option
value=${source.pk}
?selected=${selected}
>
${source.name} (${source.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.oauthSources?.results.map((source) => {
const selected = (this.instance?.jwksSources || []).some((su) => {
return su == source.pk;
});
return html`<option value=${source.pk} ?selected=${selected}>
${source.name} (${source.slug})
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.`}

View File

@ -9,9 +9,9 @@ import "@goauthentik/elements/forms/SearchSelect";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { TemplateResult, html } from "lit-html";
import { TemplateResult, html } from "lit";
import { ifDefined } from "lit-html/directives/if-defined.js";
import { customElement } from "lit/decorators.js";
import {
Flow,

View File

@ -11,7 +11,8 @@ import "@goauthentik/elements/events/ObjectChangelog";
import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, customElement, html, property } from "lit-element";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";

View File

@ -12,7 +12,6 @@ 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 {
CertificateKeyPair,
@ -23,6 +22,7 @@ import {
FlowsApi,
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
PaginatedSAMLPropertyMappingList,
PropertymappingsApi,
PropertymappingsSamlListRequest,
ProvidersApi,
@ -40,6 +40,16 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
});
}
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsSamlList({
ordering: "saml_name",
});
}
propertyMappings?: PaginatedSAMLPropertyMappingList;
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated provider.`;
@ -241,36 +251,27 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsSamlList({
ordering: "saml_name",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
mapping.managed?.startsWith(
"goauthentik.io/providers/saml",
) || false;
} else {
selected = Array.from(
this.instance?.propertyMappings,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
mapping.managed?.startsWith(
"goauthentik.io/providers/saml",
) || false;
} else {
selected = Array.from(this.instance?.propertyMappings).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}

View File

@ -11,12 +11,12 @@ 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 {
CoreApi,
CoreGroupsListRequest,
Group,
PaginatedSCIMMappingList,
PropertymappingsApi,
ProvidersApi,
SCIMProvider,
@ -30,6 +30,16 @@ export class SCIMProviderFormPage extends ModelForm<SCIMProvider, number> {
});
}
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsScimList({
ordering: "managed",
});
}
propertyMappings?: PaginatedSCIMMappingList;
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated provider.`;
@ -147,36 +157,26 @@ export class SCIMProviderFormPage extends ModelForm<SCIMProvider, number> {
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScimList({
ordering: "managed",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
mapping.managed ===
"goauthentik.io/providers/scim/user" ||
false;
} else {
selected = Array.from(
this.instance?.propertyMappings,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
mapping.managed === "goauthentik.io/providers/scim/user" ||
false;
} else {
selected = Array.from(this.instance?.propertyMappings).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Property mappings used to user mapping.`}
@ -191,35 +191,25 @@ export class SCIMProviderFormPage extends ModelForm<SCIMProvider, number> {
name="propertyMappingsGroup"
>
<select class="pf-c-form-control" multiple>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScimList({
ordering: "managed",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappingsGroup) {
selected =
mapping.managed ===
"goauthentik.io/providers/scim/group";
} else {
selected = Array.from(
this.instance?.propertyMappingsGroup,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappingsGroup) {
selected =
mapping.managed === "goauthentik.io/providers/scim/group";
} else {
selected = Array.from(
this.instance?.propertyMappingsGroup,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Property mappings used to group creation.`}

View File

@ -10,7 +10,6 @@ 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 {
CertificateKeyPair,
@ -21,6 +20,7 @@ import {
Group,
LDAPSource,
LDAPSourceRequest,
PaginatedLDAPPropertyMappingList,
PropertymappingsApi,
SourcesApi,
} from "@goauthentik/api";
@ -33,6 +33,16 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
});
}
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsLdapList({
ordering: "managed,object_field",
});
}
propertyMappings?: PaginatedLDAPPropertyMappingList;
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated source.`;
@ -241,40 +251,31 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsLdapList({
ordering: "managed,object_field",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
mapping.managed?.startsWith(
"goauthentik.io/sources/ldap/default",
) ||
mapping.managed?.startsWith(
"goauthentik.io/sources/ldap/ms",
) ||
false;
} else {
selected = Array.from(
this.instance?.propertyMappings,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappings) {
selected =
mapping.managed?.startsWith(
"goauthentik.io/sources/ldap/default",
) ||
mapping.managed?.startsWith(
"goauthentik.io/sources/ldap/ms",
) ||
false;
} else {
selected = Array.from(this.instance?.propertyMappings).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Property mappings used to user creation.`}
@ -289,35 +290,26 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
name="propertyMappingsGroup"
>
<select class="pf-c-form-control" multiple>
${until(
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsLdapList({
ordering: "managed,object_field",
})
.then((mappings) => {
return mappings.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappingsGroup) {
selected =
mapping.managed ===
"goauthentik.io/sources/ldap/default-name";
} else {
selected = Array.from(
this.instance?.propertyMappingsGroup,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!this.instance?.propertyMappingsGroup) {
selected =
mapping.managed ===
"goauthentik.io/sources/ldap/default-name";
} else {
selected = Array.from(
this.instance?.propertyMappingsGroup,
).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Property mappings used to group creation.`}

View File

@ -2,6 +2,7 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
@ -13,7 +14,6 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
CapabilitiesEnum,
@ -315,63 +315,52 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
${t`Path template for users created. Use placeholders like \`%(slug)s\` to insert the source slug.`}
</p>
</ak-form-element-horizontal>
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.icon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.icon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
<span class="pf-c-switch__label">
${t`Clear icon`}
</span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input
type="text"
value="${first(this.instance?.icon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
</p>
</ak-form-element-horizontal>`;
}),
)}
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
? html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.icon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.icon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label"> ${t`Clear icon`} </span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input
type="text"
value="${first(this.instance?.icon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
</p>
</ak-form-element-horizontal>`}
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Protocol settings`} </span>

View File

@ -3,6 +3,7 @@ import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
import { first, randomString } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
@ -13,7 +14,6 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
CapabilitiesEnum,
@ -267,63 +267,52 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
${t`Path template for users created. Use placeholders like \`%(slug)s\` to insert the source slug.`}
</p>
</ak-form-element-horizontal>
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.icon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.icon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
<span class="pf-c-switch__label">
${t`Clear icon`}
</span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input
type="text"
value="${first(this.instance?.icon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
</p>
</ak-form-element-horizontal>`;
}),
)}
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
? html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.icon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.icon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label"> ${t`Clear icon`} </span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input
type="text"
value="${first(this.instance?.icon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
</p>
</ak-form-element-horizontal>`}
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Protocol settings`} </span>
<div slot="body" class="pf-c-form">

View File

@ -2,6 +2,7 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
@ -13,7 +14,6 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import {
BindingTypeEnum,
@ -161,62 +161,52 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
</option>
</select>
</ak-form-element-horizontal>
${until(
config().then((c) => {
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.icon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.icon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
<span class="pf-c-switch__label">
${t`Clear icon`}
</span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`;
}
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input
type="text"
value="${first(this.instance?.icon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
</p>
</ak-form-element-horizontal>`;
}),
)}
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
? html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input type="file" value="" class="pf-c-form-control" />
${this.instance?.icon
? html`
<p class="pf-c-form__helper-text">
${t`Currently set to:`} ${this.instance?.icon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.icon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target = ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label"> ${t`Clear icon`} </span>
</label>
<p class="pf-c-form__helper-text">
${t`Delete currently set icon.`}
</p>
</ak-form-element-horizontal>
`
: html``}`
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
<input
type="text"
value="${first(this.instance?.icon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
</p>
</ak-form-element-horizontal>`}
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Protocol settings`} </span>

View File

@ -10,30 +10,35 @@ 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 {
AuthenticatorValidateStage,
DeviceClassesEnum,
NotConfiguredActionEnum,
PaginatedStageList,
StagesApi,
UserVerificationEnum,
} from "@goauthentik/api";
@customElement("ak-stage-authenticator-validate-form")
export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValidateStage, string> {
loadInstance(pk: string): Promise<AuthenticatorValidateStage> {
return new StagesApi(DEFAULT_CONFIG)
.stagesAuthenticatorValidateRetrieve({
stageUuid: pk,
})
.then((stage) => {
this.showConfigurationStages =
stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
return stage;
});
async loadInstance(pk: string): Promise<AuthenticatorValidateStage> {
const stage = await new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorValidateRetrieve({
stageUuid: pk,
});
this.showConfigurationStages =
stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
return stage;
}
async load(): Promise<void> {
this.stages = await new StagesApi(DEFAULT_CONFIG).stagesAllList({
ordering: "name",
});
}
stages?: PaginatedStageList;
@property({ type: Boolean })
showConfigurationStages = true;
@ -216,28 +221,19 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
name="configurationStages"
>
<select class="pf-c-form-control" multiple>
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesAllList({
ordering: "name",
})
.then((stages) => {
return stages.results.map((stage) => {
const selected = Array.from(
this.instance?.configurationStages || [],
).some((su) => {
return su == stage.pk;
});
return html`<option
value=${ifDefined(stage.pk)}
?selected=${selected}
>
${stage.name} (${stage.verboseName})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.stages?.results.map((stage) => {
const selected = Array.from(
this.instance?.configurationStages || [],
).some((su) => {
return su == stage.pk;
});
return html`<option
value=${ifDefined(stage.pk)}
?selected=${selected}
>
${stage.name} (${stage.verboseName})
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`}

View File

@ -9,23 +9,25 @@ 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 { EmailStage, StagesApi } from "@goauthentik/api";
import { EmailStage, StagesApi, TypeCreate } from "@goauthentik/api";
@customElement("ak-stage-email-form")
export class EmailStageForm extends ModelForm<EmailStage, string> {
loadInstance(pk: string): Promise<EmailStage> {
return new StagesApi(DEFAULT_CONFIG)
.stagesEmailRetrieve({
stageUuid: pk,
})
.then((stage) => {
this.showConnectionSettings = !stage.useGlobalSettings;
return stage;
});
async loadInstance(pk: string): Promise<EmailStage> {
const stage = await new StagesApi(DEFAULT_CONFIG).stagesEmailRetrieve({
stageUuid: pk,
});
this.showConnectionSettings = !stage.useGlobalSettings;
return stage;
}
async load(): Promise<void> {
this.templates = await new StagesApi(DEFAULT_CONFIG).stagesEmailTemplatesList();
}
templates?: TypeCreate[];
@property({ type: Boolean })
showConnectionSettings = false;
@ -232,23 +234,15 @@ export class EmailStageForm extends ModelForm<EmailStage, string> {
name="template"
>
<select name="users" class="pf-c-form-control">
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesEmailTemplatesList()
.then((templates) => {
return templates.map((template) => {
const selected =
this.instance?.template === template.name;
return html`<option
value=${ifDefined(template.name)}
?selected=${selected}
>
${template.description}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.templates?.map((template) => {
const selected = this.instance?.template === template.name;
return html`<option
value=${ifDefined(template.name)}
?selected=${selected}
>
${template.description}
</option>`;
})}
</select>
</ak-form-element-horizontal>
</div>

View File

@ -11,7 +11,6 @@ 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 {
Flow,
@ -19,6 +18,7 @@ import {
FlowsInstancesListDesignationEnum,
FlowsInstancesListRequest,
IdentificationStage,
PaginatedSourceList,
SourcesApi,
Stage,
StagesApi,
@ -34,6 +34,14 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
});
}
async load(): Promise<void> {
this.sources = await new SourcesApi(DEFAULT_CONFIG).sourcesAllList({
ordering: "slug",
});
}
sources?: PaginatedSourceList;
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated stage.`;
@ -80,7 +88,7 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`User fields`} name="userFields">
<select name="users" class="pf-c-form-control" multiple>
<select class="pf-c-form-control" multiple>
<option
value=${UserFieldsEnum.Username}
?selected=${this.isUserFieldSelected(UserFieldsEnum.Username)}
@ -187,35 +195,28 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
name="sources"
>
<select class="pf-c-form-control" multiple>
${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesAllList({})
.then((sources) => {
return sources.results.map((source) => {
let selected = Array.from(
this.instance?.sources || [],
).some((su) => {
return su == source.pk;
});
// Creating a new instance, auto-select built-in source
// Only when no other sources exist
if (
!this.instance &&
source.component === "" &&
sources.results.length < 2
) {
selected = true;
}
return html`<option
value=${ifDefined(source.pk)}
?selected=${selected}
>
${source.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.sources?.results.map((source) => {
let selected = Array.from(this.instance?.sources || []).some(
(su) => {
return su == source.pk;
},
);
// Creating a new instance, auto-select built-in source
// Only when no other sources exist
if (
!this.instance &&
source.component === "" &&
(this.sources?.results || []).length < 2
) {
selected = true;
}
return html`<option
value=${ifDefined(source.pk)}
?selected=${selected}
>
${source.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP.`}

View File

@ -10,9 +10,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 { PoliciesApi, PromptStage, StagesApi } from "@goauthentik/api";
import {
PaginatedPolicyList,
PaginatedPromptList,
PoliciesApi,
PromptStage,
StagesApi,
} from "@goauthentik/api";
@customElement("ak-stage-prompt-form")
export class PromptStageForm extends ModelForm<PromptStage, string> {
@ -22,6 +27,18 @@ export class PromptStageForm extends ModelForm<PromptStage, string> {
});
}
async load(): Promise<void> {
this.prompts = await new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsList({
ordering: "field_name",
});
this.policies = await new PoliciesApi(DEFAULT_CONFIG).policiesAllList({
ordering: "name",
});
}
prompts?: PaginatedPromptList;
policies?: PaginatedPolicyList;
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated stage.`;
@ -61,28 +78,19 @@ export class PromptStageForm extends ModelForm<PromptStage, string> {
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Fields`} ?required=${true} name="fields">
<select name="users" class="pf-c-form-control" multiple>
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesPromptPromptsList({
ordering: "field_name",
})
.then((prompts) => {
return prompts.results.map((prompt) => {
const selected = Array.from(
this.instance?.fields || [],
).some((su) => {
return su == prompt.pk;
});
return html`<option
value=${ifDefined(prompt.pk)}
?selected=${selected}
>
${t`${prompt.name} ("${prompt.fieldKey}", of type ${prompt.type})`}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.prompts?.results.map((prompt) => {
const selected = Array.from(this.instance?.fields || []).some(
(su) => {
return su == prompt.pk;
},
);
return html`<option
value=${ifDefined(prompt.pk)}
?selected=${selected}
>
${t`${prompt.name} ("${prompt.fieldKey}", of type ${prompt.type})`}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Hold control/command to select multiple items.`}
@ -101,28 +109,19 @@ export class PromptStageForm extends ModelForm<PromptStage, string> {
name="validationPolicies"
>
<select name="users" class="pf-c-form-control" multiple>
${until(
new PoliciesApi(DEFAULT_CONFIG)
.policiesAllList({
ordering: "name",
})
.then((policies) => {
return policies.results.map((policy) => {
const selected = Array.from(
this.instance?.validationPolicies || [],
).some((su) => {
return su == policy.pk;
});
return html`<option
value=${ifDefined(policy.pk)}
?selected=${selected}
>
${t`${policy.name} (${policy.verboseName})`}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
${this.policies?.results.map((policy) => {
const selected = Array.from(
this.instance?.validationPolicies || [],
).some((su) => {
return su == policy.pk;
});
return html`<option
value=${ifDefined(policy.pk)}
?selected=${selected}
>
${t`${policy.name} (${policy.verboseName})`}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${t`Selected policies are executed when the stage is submitted to validate the data.`}