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:
parent
20522558fe
commit
14f0034a0a
|
@ -1,5 +1,6 @@
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { first, groupBy } from "@goauthentik/common/utils";
|
import { first, groupBy } from "@goauthentik/common/utils";
|
||||||
|
import { rootInterface } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import "@goauthentik/elements/forms/ModalForm";
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
@ -13,7 +14,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Application,
|
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.`}
|
${t`If checked, the launch URL will open in a new browser tab or window from the user's application library.`}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${until(
|
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
|
||||||
config().then((c) => {
|
? html`<ak-form-element-horizontal label=${t`Icon`} name="metaIcon">
|
||||||
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
<input type="file" value="" class="pf-c-form-control" />
|
||||||
return html`<ak-form-element-horizontal
|
${this.instance?.metaIcon
|
||||||
label=${t`Icon`}
|
? html`
|
||||||
name="metaIcon"
|
<p class="pf-c-form__helper-text">
|
||||||
>
|
${t`Currently set to:`} ${this.instance?.metaIcon}
|
||||||
<input type="file" value="" class="pf-c-form-control" />
|
</p>
|
||||||
${this.instance?.metaIcon
|
`
|
||||||
? html`
|
: html``}
|
||||||
<p class="pf-c-form__helper-text">
|
</ak-form-element-horizontal>
|
||||||
${t`Currently set to:`}
|
${this.instance?.metaIcon
|
||||||
${this.instance?.metaIcon}
|
? html`
|
||||||
</p>
|
<ak-form-element-horizontal>
|
||||||
`
|
<label class="pf-c-switch">
|
||||||
: html``}
|
<input
|
||||||
</ak-form-element-horizontal>
|
class="pf-c-switch__input"
|
||||||
${this.instance?.metaIcon
|
type="checkbox"
|
||||||
? html`
|
@change=${(ev: Event) => {
|
||||||
<ak-form-element-horizontal>
|
const target =
|
||||||
<label class="pf-c-switch">
|
ev.target as HTMLInputElement;
|
||||||
<input
|
this.clearIcon = target.checked;
|
||||||
class="pf-c-switch__input"
|
}}
|
||||||
type="checkbox"
|
/>
|
||||||
@change=${(ev: Event) => {
|
<span class="pf-c-switch__toggle">
|
||||||
const target =
|
<span class="pf-c-switch__toggle-icon">
|
||||||
ev.target as HTMLInputElement;
|
<i
|
||||||
this.clearIcon = target.checked;
|
class="fas fa-check"
|
||||||
}}
|
aria-hidden="true"
|
||||||
/>
|
></i>
|
||||||
<span class="pf-c-switch__toggle">
|
</span>
|
||||||
<span class="pf-c-switch__toggle-icon">
|
</span>
|
||||||
<i
|
<span class="pf-c-switch__label">
|
||||||
class="fas fa-check"
|
${t`Clear icon`}
|
||||||
aria-hidden="true"
|
</span>
|
||||||
></i>
|
</label>
|
||||||
</span>
|
<p class="pf-c-form__helper-text">
|
||||||
</span>
|
${t`Delete currently set icon.`}
|
||||||
<span class="pf-c-switch__label">
|
</p>
|
||||||
${t`Clear icon`}
|
</ak-form-element-horizontal>
|
||||||
</span>
|
`
|
||||||
</label>
|
: html``}`
|
||||||
<p class="pf-c-form__helper-text">
|
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
${t`Delete currently set icon.`}
|
<input
|
||||||
</p>
|
type="text"
|
||||||
</ak-form-element-horizontal>
|
value="${first(this.instance?.metaIcon, "")}"
|
||||||
`
|
class="pf-c-form-control"
|
||||||
: html``}`;
|
/>
|
||||||
}
|
<p class="pf-c-form__helper-text">
|
||||||
return html`<ak-form-element-horizontal
|
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
|
||||||
label=${t`Icon`}
|
</p>
|
||||||
name="metaIcon"
|
</ak-form-element-horizontal>`}
|
||||||
>
|
|
||||||
<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">
|
<ak-form-element-horizontal label=${t`Publisher`} name="metaPublisher">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CoreApi,
|
CoreApi,
|
||||||
|
@ -17,17 +16,26 @@ import {
|
||||||
EventsApi,
|
EventsApi,
|
||||||
Group,
|
Group,
|
||||||
NotificationRule,
|
NotificationRule,
|
||||||
|
PaginatedNotificationTransportList,
|
||||||
SeverityEnum,
|
SeverityEnum,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-event-rule-form")
|
@customElement("ak-event-rule-form")
|
||||||
export class RuleForm extends ModelForm<NotificationRule, string> {
|
export class RuleForm extends ModelForm<NotificationRule, string> {
|
||||||
|
eventTransports?: PaginatedNotificationTransportList;
|
||||||
|
|
||||||
loadInstance(pk: string): Promise<NotificationRule> {
|
loadInstance(pk: string): Promise<NotificationRule> {
|
||||||
return new EventsApi(DEFAULT_CONFIG).eventsRulesRetrieve({
|
return new EventsApi(DEFAULT_CONFIG).eventsRulesRetrieve({
|
||||||
pbmUuid: pk,
|
pbmUuid: pk,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async load(): Promise<void> {
|
||||||
|
this.eventTransports = await new EventsApi(DEFAULT_CONFIG).eventsTransportsList({
|
||||||
|
ordering: "name",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getSuccessMessage(): string {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated rule.`;
|
return t`Successfully updated rule.`;
|
||||||
|
@ -86,28 +94,14 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${t`Transports`} ?required=${true} name="transports">
|
<ak-form-element-horizontal label=${t`Transports`} ?required=${true} name="transports">
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.eventTransports?.results.map((transport) => {
|
||||||
new EventsApi(DEFAULT_CONFIG)
|
const selected = Array.from(this.instance?.transports || []).some((su) => {
|
||||||
.eventsTransportsList({
|
return su == transport.pk;
|
||||||
ordering: "name",
|
});
|
||||||
})
|
return html`<option value=${ifDefined(transport.pk)} ?selected=${selected}>
|
||||||
.then((transports) => {
|
${transport.name}
|
||||||
return transports.results.map((transport) => {
|
</option>`;
|
||||||
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>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<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.`}
|
${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
|
<ak-radio
|
||||||
.options=${[
|
.options=${[
|
||||||
{
|
{
|
||||||
label: "Alert",
|
label: t`Alert`,
|
||||||
value: SeverityEnum.Alert,
|
value: SeverityEnum.Alert,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
import "@goauthentik/elements/forms/Radio";
|
||||||
import "@goauthentik/elements/forms/SearchSelect";
|
import "@goauthentik/elements/forms/SearchSelect";
|
||||||
|
|
||||||
import { t } from "@lingui/macro";
|
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 {
|
onModeChange(mode: string | undefined): void {
|
||||||
if (
|
if (
|
||||||
mode === NotificationTransportModeEnum.Webhook ||
|
mode === NotificationTransportModeEnum.Webhook ||
|
||||||
|
@ -107,15 +79,32 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${t`Mode`} ?required=${true} name="mode">
|
<ak-form-element-horizontal label=${t`Mode`} ?required=${true} name="mode">
|
||||||
<select
|
<ak-radio
|
||||||
class="pf-c-form-control"
|
@change=${(ev: CustomEvent<NotificationTransportModeEnum>) => {
|
||||||
@change=${(ev: Event) => {
|
this.onModeChange(ev.detail);
|
||||||
const current = (ev.target as HTMLInputElement).value;
|
|
||||||
this.onModeChange(current);
|
|
||||||
}}
|
}}
|
||||||
|
.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()}
|
</ak-radio>
|
||||||
</select>
|
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
?hidden=${!this.showWebhook}
|
?hidden=${!this.showWebhook}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/util
|
||||||
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum";
|
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
|
import { rootInterface } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
@ -12,7 +13,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CapabilitiesEnum,
|
CapabilitiesEnum,
|
||||||
|
@ -315,73 +315,62 @@ export class FlowForm extends ModelForm<Flow, string> {
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${until(
|
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
|
||||||
config().then((c) => {
|
? html`<ak-form-element-horizontal label=${t`Background`} name="background">
|
||||||
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
<input type="file" value="" class="pf-c-form-control" />
|
||||||
return html`<ak-form-element-horizontal
|
${this.instance?.background
|
||||||
label=${t`Background`}
|
? html`
|
||||||
name="background"
|
<p class="pf-c-form__helper-text">
|
||||||
>
|
${t`Currently set to:`} ${this.instance?.background}
|
||||||
<input type="file" value="" class="pf-c-form-control" />
|
</p>
|
||||||
${this.instance?.background
|
`
|
||||||
? html`
|
: html``}
|
||||||
<p class="pf-c-form__helper-text">
|
|
||||||
${t`Currently set to:`}
|
<p class="pf-c-form__helper-text">
|
||||||
${this.instance?.background}
|
${t`Background shown during execution.`}
|
||||||
</p>
|
</p>
|
||||||
`
|
</ak-form-element-horizontal>
|
||||||
: html``}
|
${this.instance?.background
|
||||||
<p class="pf-c-form__helper-text">
|
? html`
|
||||||
${t`Background shown during execution.`}
|
<ak-form-element-horizontal>
|
||||||
</p>
|
<label class="pf-c-switch">
|
||||||
</ak-form-element-horizontal>
|
<input
|
||||||
${this.instance?.background
|
class="pf-c-switch__input"
|
||||||
? html`
|
type="checkbox"
|
||||||
<ak-form-element-horizontal>
|
@change=${(ev: Event) => {
|
||||||
<label class="pf-c-switch">
|
const target =
|
||||||
<input
|
ev.target as HTMLInputElement;
|
||||||
class="pf-c-switch__input"
|
this.clearBackground = target.checked;
|
||||||
type="checkbox"
|
}}
|
||||||
@change=${(ev: Event) => {
|
/>
|
||||||
const target =
|
<span class="pf-c-switch__toggle">
|
||||||
ev.target as HTMLInputElement;
|
<span class="pf-c-switch__toggle-icon">
|
||||||
this.clearBackground = target.checked;
|
<i
|
||||||
}}
|
class="fas fa-check"
|
||||||
/>
|
aria-hidden="true"
|
||||||
<span class="pf-c-switch__toggle">
|
></i>
|
||||||
<span class="pf-c-switch__toggle-icon">
|
</span>
|
||||||
<i
|
</span>
|
||||||
class="fas fa-check"
|
<span class="pf-c-switch__label">
|
||||||
aria-hidden="true"
|
${t`Clear background`}
|
||||||
></i>
|
</span>
|
||||||
</span>
|
</label>
|
||||||
</span>
|
<p class="pf-c-form__helper-text">
|
||||||
<span class="pf-c-switch__label">
|
${t`Delete currently set background image.`}
|
||||||
${t`Clear icon`}
|
</p>
|
||||||
</span>
|
</ak-form-element-horizontal>
|
||||||
</label>
|
`
|
||||||
<p class="pf-c-form__helper-text">
|
: html``}`
|
||||||
${t`Delete currently set background image.`}
|
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
</p>
|
<input
|
||||||
</ak-form-element-horizontal>
|
type="text"
|
||||||
`
|
value="${first(this.instance?.background, "")}"
|
||||||
: html``}`;
|
class="pf-c-form-control"
|
||||||
}
|
/>
|
||||||
return html`<ak-form-element-horizontal
|
<p class="pf-c-form__helper-text">
|
||||||
label=${t`Background`}
|
${t`Background shown during execution.`}
|
||||||
name="background"
|
</p>
|
||||||
>
|
</ak-form-element-horizontal>`}
|
||||||
<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>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
</form>`;
|
</form>`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first, groupBy } from "@goauthentik/common/utils";
|
import { first, groupBy } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
@ -10,11 +11,13 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Flow,
|
||||||
FlowStageBinding,
|
FlowStageBinding,
|
||||||
FlowsApi,
|
FlowsApi,
|
||||||
|
FlowsInstancesListDesignationEnum,
|
||||||
|
FlowsInstancesListRequest,
|
||||||
InvalidResponseActionEnum,
|
InvalidResponseActionEnum,
|
||||||
PolicyEngineMode,
|
PolicyEngineMode,
|
||||||
Stage,
|
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">
|
return html`<ak-form-element-horizontal label=${t`Target`} ?required=${true} name="target">
|
||||||
<select class="pf-c-form-control">
|
<ak-search-select
|
||||||
${until(
|
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
|
||||||
new FlowsApi(DEFAULT_CONFIG)
|
const args: FlowsInstancesListRequest = {
|
||||||
.flowsInstancesList({
|
ordering: "slug",
|
||||||
ordering: "slug",
|
designation: FlowsInstancesListDesignationEnum.Authorization,
|
||||||
})
|
};
|
||||||
.then((flows) => {
|
if (query !== undefined) {
|
||||||
return flows.results.map((flow) => {
|
args.search = query;
|
||||||
// No ?selected check here, as this input isn't shown on update forms
|
}
|
||||||
return html`<option value=${ifDefined(flow.pk)}>
|
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
|
||||||
${flow.name} (${flow.slug})
|
return flows.results;
|
||||||
</option>`;
|
}}
|
||||||
});
|
.renderElement=${(flow: Flow): string => {
|
||||||
}),
|
return RenderFlowOption(flow);
|
||||||
html`<option>${t`Loading...`}</option>`,
|
}}
|
||||||
)}
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
</select>
|
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>`;
|
</ak-form-element-horizontal>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,18 @@ import YAML from "yaml";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
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 { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Outpost,
|
Outpost,
|
||||||
|
OutpostDefaultConfig,
|
||||||
OutpostTypeEnum,
|
OutpostTypeEnum,
|
||||||
OutpostsApi,
|
OutpostsApi,
|
||||||
OutpostsServiceConnectionsAllListRequest,
|
OutpostsServiceConnectionsAllListRequest,
|
||||||
|
PaginatedLDAPProviderList,
|
||||||
|
PaginatedProxyProviderList,
|
||||||
|
PaginatedRadiusProviderList,
|
||||||
ProvidersApi,
|
ProvidersApi,
|
||||||
ServiceConnection,
|
ServiceConnection,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
@ -31,6 +34,14 @@ export class OutpostForm extends ModelForm<Outpost, string> {
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
embedded = false;
|
embedded = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
providers?:
|
||||||
|
| PaginatedProxyProviderList
|
||||||
|
| PaginatedLDAPProviderList
|
||||||
|
| PaginatedRadiusProviderList;
|
||||||
|
|
||||||
|
defaultConfig?: OutpostDefaultConfig;
|
||||||
|
|
||||||
async loadInstance(pk: string): Promise<Outpost> {
|
async loadInstance(pk: string): Promise<Outpost> {
|
||||||
const o = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesRetrieve({
|
const o = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesRetrieve({
|
||||||
uuid: pk,
|
uuid: pk,
|
||||||
|
@ -39,6 +50,34 @@ export class OutpostForm extends ModelForm<Outpost, string> {
|
||||||
return o;
|
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 {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated outpost.`;
|
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 {
|
renderForm(): TemplateResult {
|
||||||
return html`<form class="pf-c-form pf-m-horizontal">
|
return html`<form class="pf-c-form pf-m-horizontal">
|
||||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
<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) => {
|
@change=${(ev: Event) => {
|
||||||
const target = ev.target as HTMLSelectElement;
|
const target = ev.target as HTMLSelectElement;
|
||||||
this.type = target.selectedOptions[0].value as OutpostTypeEnum;
|
this.type = target.selectedOptions[0].value as OutpostTypeEnum;
|
||||||
|
this.load();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
|
@ -162,6 +130,12 @@ export class OutpostForm extends ModelForm<Outpost, string> {
|
||||||
>
|
>
|
||||||
${t`LDAP`}
|
${t`LDAP`}
|
||||||
</option>
|
</option>
|
||||||
|
<option
|
||||||
|
value=${OutpostTypeEnum.Radius}
|
||||||
|
?selected=${this.instance?.type === OutpostTypeEnum.Radius}
|
||||||
|
>
|
||||||
|
${t`Radius`}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${t`Integration`} name="serviceConnection">
|
<ak-form-element-horizontal label=${t`Integration`} name="serviceConnection">
|
||||||
|
@ -213,7 +187,14 @@ export class OutpostForm extends ModelForm<Outpost, string> {
|
||||||
name="providers"
|
name="providers"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<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>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`You can only select providers that match the type of the outpost.`}
|
${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>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${t`Configuration`} name="config">
|
<ak-form-element-horizontal label=${t`Configuration`} name="config">
|
||||||
<!-- @ts-ignore -->
|
|
||||||
<ak-codemirror
|
<ak-codemirror
|
||||||
mode="yaml"
|
mode="yaml"
|
||||||
value="${until(
|
value="${YAML.stringify(
|
||||||
new OutpostsApi(DEFAULT_CONFIG)
|
this.instance ? this.instance.config : this.defaultConfig?.config,
|
||||||
.outpostsInstancesDefaultSettingsRetrieve()
|
|
||||||
.then((config) => {
|
|
||||||
let fc = config.config;
|
|
||||||
if (this.instance) {
|
|
||||||
fc = this.instance.config;
|
|
||||||
}
|
|
||||||
return YAML.stringify(fc);
|
|
||||||
}),
|
|
||||||
)}"
|
)}"
|
||||||
></ak-codemirror>
|
></ak-codemirror>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
|
|
|
@ -10,9 +10,15 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.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")
|
@customElement("ak-policy-event-matcher-form")
|
||||||
export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string> {
|
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 {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated policy.`;
|
return t`Successfully updated policy.`;
|
||||||
|
@ -118,19 +130,14 @@ export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string
|
||||||
<option value="" ?selected=${this.instance?.app === undefined}>
|
<option value="" ?selected=${this.instance?.app === undefined}>
|
||||||
---------
|
---------
|
||||||
</option>
|
</option>
|
||||||
${until(
|
${this.apps?.map((app) => {
|
||||||
new AdminApi(DEFAULT_CONFIG).adminAppsList().then((apps) => {
|
return html`<option
|
||||||
return apps.map((app) => {
|
value=${app.name}
|
||||||
return html`<option
|
?selected=${this.instance?.app === app.name}
|
||||||
value=${app.name}
|
>
|
||||||
?selected=${this.instance?.app === app.name}
|
${app.label}
|
||||||
>
|
</option>`;
|
||||||
${app.label}
|
})}
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Match events created by selected application. When left empty, all applications are matched.`}
|
${t`Match events created by selected application. When left empty, all applications are matched.`}
|
||||||
|
|
|
@ -11,9 +11,8 @@ import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
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 { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CertificateKeyPair,
|
CertificateKeyPair,
|
||||||
|
@ -26,6 +25,8 @@ import {
|
||||||
FlowsInstancesListRequest,
|
FlowsInstancesListRequest,
|
||||||
IssuerModeEnum,
|
IssuerModeEnum,
|
||||||
OAuth2Provider,
|
OAuth2Provider,
|
||||||
|
PaginatedOAuthSourceList,
|
||||||
|
PaginatedScopeMappingList,
|
||||||
PropertymappingsApi,
|
PropertymappingsApi,
|
||||||
ProvidersApi,
|
ProvidersApi,
|
||||||
SourcesApi,
|
SourcesApi,
|
||||||
|
@ -34,19 +35,31 @@ import {
|
||||||
|
|
||||||
@customElement("ak-provider-oauth2-form")
|
@customElement("ak-provider-oauth2-form")
|
||||||
export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
|
export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
|
||||||
loadInstance(pk: number): Promise<OAuth2Provider> {
|
propertyMappings?: PaginatedScopeMappingList;
|
||||||
return new ProvidersApi(DEFAULT_CONFIG)
|
oauthSources?: PaginatedOAuthSourceList;
|
||||||
.providersOauth2Retrieve({
|
|
||||||
id: pk,
|
@state()
|
||||||
})
|
showClientSecret = true;
|
||||||
.then((provider) => {
|
|
||||||
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
|
async loadInstance(pk: number): Promise<OAuth2Provider> {
|
||||||
return provider;
|
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Retrieve({
|
||||||
});
|
id: pk,
|
||||||
|
});
|
||||||
|
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
|
||||||
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property({ type: Boolean })
|
async load(): Promise<void> {
|
||||||
showClientSecret = true;
|
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 {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
|
@ -287,36 +300,27 @@ ${this.instance?.redirectUris}</textarea
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
|
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.propertyMappings?.results.map((scope) => {
|
||||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
let selected = false;
|
||||||
.propertymappingsScopeList({
|
if (!this.instance?.propertyMappings) {
|
||||||
ordering: "scope_name",
|
selected =
|
||||||
})
|
scope.managed?.startsWith(
|
||||||
.then((scopes) => {
|
"goauthentik.io/providers/oauth2/scope-",
|
||||||
return scopes.results.map((scope) => {
|
) || false;
|
||||||
let selected = false;
|
} else {
|
||||||
if (!this.instance?.propertyMappings) {
|
selected = Array.from(this.instance?.propertyMappings).some(
|
||||||
selected =
|
(su) => {
|
||||||
scope.managed?.startsWith(
|
return su == scope.pk;
|
||||||
"goauthentik.io/providers/oauth2/scope-",
|
},
|
||||||
) || false;
|
);
|
||||||
} else {
|
}
|
||||||
selected = Array.from(
|
return html`<option
|
||||||
this.instance?.propertyMappings,
|
value=${ifDefined(scope.pk)}
|
||||||
).some((su) => {
|
?selected=${selected}
|
||||||
return su == scope.pk;
|
>
|
||||||
});
|
${scope.name}
|
||||||
}
|
</option>`;
|
||||||
return html`<option
|
})}
|
||||||
value=${ifDefined(scope.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${scope.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<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.`}
|
${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">
|
<div slot="body" class="pf-c-form">
|
||||||
<ak-form-element-horizontal label=${t`Trusted OIDC Sources`} name="jwksSources">
|
<ak-form-element-horizontal label=${t`Trusted OIDC Sources`} name="jwksSources">
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.oauthSources?.results.map((source) => {
|
||||||
new SourcesApi(DEFAULT_CONFIG)
|
const selected = (this.instance?.jwksSources || []).some((su) => {
|
||||||
.sourcesOauthList({
|
return su == source.pk;
|
||||||
ordering: "name",
|
});
|
||||||
hasJwks: true,
|
return html`<option value=${source.pk} ?selected=${selected}>
|
||||||
})
|
${source.name} (${source.slug})
|
||||||
.then((sources) => {
|
</option>`;
|
||||||
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>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<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.`}
|
${t`JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.`}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { CSSResult, css } from "lit";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.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 PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||||
import PFList from "@patternfly/patternfly/components/List/list.css";
|
import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||||
|
@ -28,6 +27,8 @@ import {
|
||||||
FlowsApi,
|
FlowsApi,
|
||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
FlowsInstancesListRequest,
|
FlowsInstancesListRequest,
|
||||||
|
PaginatedOAuthSourceList,
|
||||||
|
PaginatedScopeMappingList,
|
||||||
PropertymappingsApi,
|
PropertymappingsApi,
|
||||||
ProvidersApi,
|
ProvidersApi,
|
||||||
ProxyMode,
|
ProxyMode,
|
||||||
|
@ -51,18 +52,30 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadInstance(pk: number): Promise<ProxyProvider> {
|
async loadInstance(pk: number): Promise<ProxyProvider> {
|
||||||
return new ProvidersApi(DEFAULT_CONFIG)
|
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersProxyRetrieve({
|
||||||
.providersProxyRetrieve({
|
id: pk,
|
||||||
id: pk,
|
});
|
||||||
})
|
this.showHttpBasic = first(provider.basicAuthEnabled, true);
|
||||||
.then((provider) => {
|
this.mode = first(provider.mode, ProxyMode.Proxy);
|
||||||
this.showHttpBasic = first(provider.basicAuthEnabled, true);
|
return provider;
|
||||||
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()
|
@state()
|
||||||
showHttpBasic = true;
|
showHttpBasic = true;
|
||||||
|
|
||||||
|
@ -392,34 +405,23 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.propertyMappings?.results
|
||||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
.filter((scope) => {
|
||||||
.propertymappingsScopeList({
|
return !scope.managed?.startsWith("goauthentik.io/providers");
|
||||||
ordering: "scope_name",
|
})
|
||||||
})
|
.map((scope) => {
|
||||||
.then((scopes) => {
|
const selected = (this.instance?.propertyMappings || []).some(
|
||||||
return scopes.results
|
(su) => {
|
||||||
.filter((scope) => {
|
return su == scope.pk;
|
||||||
return !scope.managed?.startsWith(
|
},
|
||||||
"goauthentik.io/providers",
|
);
|
||||||
);
|
return html`<option
|
||||||
})
|
value=${ifDefined(scope.pk)}
|
||||||
.map((scope) => {
|
?selected=${selected}
|
||||||
const selected = (
|
>
|
||||||
this.instance?.propertyMappings || []
|
${scope.name}
|
||||||
).some((su) => {
|
</option>`;
|
||||||
return su == scope.pk;
|
})}
|
||||||
});
|
|
||||||
return html`<option
|
|
||||||
value=${ifDefined(scope.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${scope.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Additional scope mappings, which are passed to the proxy.`}
|
${t`Additional scope mappings, which are passed to the proxy.`}
|
||||||
|
@ -497,29 +499,14 @@ ${this.instance?.skipPathRegex}</textarea
|
||||||
${this.showHttpBasic ? this.renderHttpBasic() : html``}
|
${this.showHttpBasic ? this.renderHttpBasic() : html``}
|
||||||
<ak-form-element-horizontal label=${t`Trusted OIDC Sources`} name="jwksSources">
|
<ak-form-element-horizontal label=${t`Trusted OIDC Sources`} name="jwksSources">
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.oauthSources?.results.map((source) => {
|
||||||
new SourcesApi(DEFAULT_CONFIG)
|
const selected = (this.instance?.jwksSources || []).some((su) => {
|
||||||
.sourcesOauthList({
|
return su == source.pk;
|
||||||
ordering: "name",
|
});
|
||||||
hasJwks: true,
|
return html`<option value=${source.pk} ?selected=${selected}>
|
||||||
})
|
${source.name} (${source.slug})
|
||||||
.then((sources) => {
|
</option>`;
|
||||||
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>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<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.`}
|
${t`JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.`}
|
||||||
|
|
|
@ -9,9 +9,9 @@ import "@goauthentik/elements/forms/SearchSelect";
|
||||||
|
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { customElement } from "lit-element";
|
import { TemplateResult, html } from "lit";
|
||||||
import { TemplateResult, html } from "lit-html";
|
|
||||||
import { ifDefined } from "lit-html/directives/if-defined.js";
|
import { ifDefined } from "lit-html/directives/if-defined.js";
|
||||||
|
import { customElement } from "lit/decorators.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Flow,
|
Flow,
|
||||||
|
|
|
@ -11,7 +11,8 @@ import "@goauthentik/elements/events/ObjectChangelog";
|
||||||
|
|
||||||
import { t } from "@lingui/macro";
|
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 PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CertificateKeyPair,
|
CertificateKeyPair,
|
||||||
|
@ -23,6 +22,7 @@ import {
|
||||||
FlowsApi,
|
FlowsApi,
|
||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
FlowsInstancesListRequest,
|
FlowsInstancesListRequest,
|
||||||
|
PaginatedSAMLPropertyMappingList,
|
||||||
PropertymappingsApi,
|
PropertymappingsApi,
|
||||||
PropertymappingsSamlListRequest,
|
PropertymappingsSamlListRequest,
|
||||||
ProvidersApi,
|
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 {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated provider.`;
|
return t`Successfully updated provider.`;
|
||||||
|
@ -241,36 +251,27 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.propertyMappings?.results.map((mapping) => {
|
||||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
let selected = false;
|
||||||
.propertymappingsSamlList({
|
if (!this.instance?.propertyMappings) {
|
||||||
ordering: "saml_name",
|
selected =
|
||||||
})
|
mapping.managed?.startsWith(
|
||||||
.then((mappings) => {
|
"goauthentik.io/providers/saml",
|
||||||
return mappings.results.map((mapping) => {
|
) || false;
|
||||||
let selected = false;
|
} else {
|
||||||
if (!this.instance?.propertyMappings) {
|
selected = Array.from(this.instance?.propertyMappings).some(
|
||||||
selected =
|
(su) => {
|
||||||
mapping.managed?.startsWith(
|
return su == mapping.pk;
|
||||||
"goauthentik.io/providers/saml",
|
},
|
||||||
) || false;
|
);
|
||||||
} else {
|
}
|
||||||
selected = Array.from(
|
return html`<option
|
||||||
this.instance?.propertyMappings,
|
value=${ifDefined(mapping.pk)}
|
||||||
).some((su) => {
|
?selected=${selected}
|
||||||
return su == mapping.pk;
|
>
|
||||||
});
|
${mapping.name}
|
||||||
}
|
</option>`;
|
||||||
return html`<option
|
})}
|
||||||
value=${ifDefined(mapping.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${mapping.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Hold control/command to select multiple items.`}
|
${t`Hold control/command to select multiple items.`}
|
||||||
|
|
|
@ -11,12 +11,12 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CoreApi,
|
CoreApi,
|
||||||
CoreGroupsListRequest,
|
CoreGroupsListRequest,
|
||||||
Group,
|
Group,
|
||||||
|
PaginatedSCIMMappingList,
|
||||||
PropertymappingsApi,
|
PropertymappingsApi,
|
||||||
ProvidersApi,
|
ProvidersApi,
|
||||||
SCIMProvider,
|
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 {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated provider.`;
|
return t`Successfully updated provider.`;
|
||||||
|
@ -147,36 +157,26 @@ export class SCIMProviderFormPage extends ModelForm<SCIMProvider, number> {
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.propertyMappings?.results.map((mapping) => {
|
||||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
let selected = false;
|
||||||
.propertymappingsScimList({
|
if (!this.instance?.propertyMappings) {
|
||||||
ordering: "managed",
|
selected =
|
||||||
})
|
mapping.managed === "goauthentik.io/providers/scim/user" ||
|
||||||
.then((mappings) => {
|
false;
|
||||||
return mappings.results.map((mapping) => {
|
} else {
|
||||||
let selected = false;
|
selected = Array.from(this.instance?.propertyMappings).some(
|
||||||
if (!this.instance?.propertyMappings) {
|
(su) => {
|
||||||
selected =
|
return su == mapping.pk;
|
||||||
mapping.managed ===
|
},
|
||||||
"goauthentik.io/providers/scim/user" ||
|
);
|
||||||
false;
|
}
|
||||||
} else {
|
return html`<option
|
||||||
selected = Array.from(
|
value=${ifDefined(mapping.pk)}
|
||||||
this.instance?.propertyMappings,
|
?selected=${selected}
|
||||||
).some((su) => {
|
>
|
||||||
return su == mapping.pk;
|
${mapping.name}
|
||||||
});
|
</option>`;
|
||||||
}
|
})}
|
||||||
return html`<option
|
|
||||||
value=${ifDefined(mapping.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${mapping.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Property mappings used to user mapping.`}
|
${t`Property mappings used to user mapping.`}
|
||||||
|
@ -191,35 +191,25 @@ export class SCIMProviderFormPage extends ModelForm<SCIMProvider, number> {
|
||||||
name="propertyMappingsGroup"
|
name="propertyMappingsGroup"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.propertyMappings?.results.map((mapping) => {
|
||||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
let selected = false;
|
||||||
.propertymappingsScimList({
|
if (!this.instance?.propertyMappingsGroup) {
|
||||||
ordering: "managed",
|
selected =
|
||||||
})
|
mapping.managed === "goauthentik.io/providers/scim/group";
|
||||||
.then((mappings) => {
|
} else {
|
||||||
return mappings.results.map((mapping) => {
|
selected = Array.from(
|
||||||
let selected = false;
|
this.instance?.propertyMappingsGroup,
|
||||||
if (!this.instance?.propertyMappingsGroup) {
|
).some((su) => {
|
||||||
selected =
|
return su == mapping.pk;
|
||||||
mapping.managed ===
|
});
|
||||||
"goauthentik.io/providers/scim/group";
|
}
|
||||||
} else {
|
return html`<option
|
||||||
selected = Array.from(
|
value=${ifDefined(mapping.pk)}
|
||||||
this.instance?.propertyMappingsGroup,
|
?selected=${selected}
|
||||||
).some((su) => {
|
>
|
||||||
return su == mapping.pk;
|
${mapping.name}
|
||||||
});
|
</option>`;
|
||||||
}
|
})}
|
||||||
return html`<option
|
|
||||||
value=${ifDefined(mapping.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${mapping.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Property mappings used to group creation.`}
|
${t`Property mappings used to group creation.`}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CertificateKeyPair,
|
CertificateKeyPair,
|
||||||
|
@ -21,6 +20,7 @@ import {
|
||||||
Group,
|
Group,
|
||||||
LDAPSource,
|
LDAPSource,
|
||||||
LDAPSourceRequest,
|
LDAPSourceRequest,
|
||||||
|
PaginatedLDAPPropertyMappingList,
|
||||||
PropertymappingsApi,
|
PropertymappingsApi,
|
||||||
SourcesApi,
|
SourcesApi,
|
||||||
} from "@goauthentik/api";
|
} 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 {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated source.`;
|
return t`Successfully updated source.`;
|
||||||
|
@ -241,40 +251,31 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.propertyMappings?.results.map((mapping) => {
|
||||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
let selected = false;
|
||||||
.propertymappingsLdapList({
|
if (!this.instance?.propertyMappings) {
|
||||||
ordering: "managed,object_field",
|
selected =
|
||||||
})
|
mapping.managed?.startsWith(
|
||||||
.then((mappings) => {
|
"goauthentik.io/sources/ldap/default",
|
||||||
return mappings.results.map((mapping) => {
|
) ||
|
||||||
let selected = false;
|
mapping.managed?.startsWith(
|
||||||
if (!this.instance?.propertyMappings) {
|
"goauthentik.io/sources/ldap/ms",
|
||||||
selected =
|
) ||
|
||||||
mapping.managed?.startsWith(
|
false;
|
||||||
"goauthentik.io/sources/ldap/default",
|
} else {
|
||||||
) ||
|
selected = Array.from(this.instance?.propertyMappings).some(
|
||||||
mapping.managed?.startsWith(
|
(su) => {
|
||||||
"goauthentik.io/sources/ldap/ms",
|
return su == mapping.pk;
|
||||||
) ||
|
},
|
||||||
false;
|
);
|
||||||
} else {
|
}
|
||||||
selected = Array.from(
|
return html`<option
|
||||||
this.instance?.propertyMappings,
|
value=${ifDefined(mapping.pk)}
|
||||||
).some((su) => {
|
?selected=${selected}
|
||||||
return su == mapping.pk;
|
>
|
||||||
});
|
${mapping.name}
|
||||||
}
|
</option>`;
|
||||||
return html`<option
|
})}
|
||||||
value=${ifDefined(mapping.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${mapping.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Property mappings used to user creation.`}
|
${t`Property mappings used to user creation.`}
|
||||||
|
@ -289,35 +290,26 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
|
||||||
name="propertyMappingsGroup"
|
name="propertyMappingsGroup"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.propertyMappings?.results.map((mapping) => {
|
||||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
let selected = false;
|
||||||
.propertymappingsLdapList({
|
if (!this.instance?.propertyMappingsGroup) {
|
||||||
ordering: "managed,object_field",
|
selected =
|
||||||
})
|
mapping.managed ===
|
||||||
.then((mappings) => {
|
"goauthentik.io/sources/ldap/default-name";
|
||||||
return mappings.results.map((mapping) => {
|
} else {
|
||||||
let selected = false;
|
selected = Array.from(
|
||||||
if (!this.instance?.propertyMappingsGroup) {
|
this.instance?.propertyMappingsGroup,
|
||||||
selected =
|
).some((su) => {
|
||||||
mapping.managed ===
|
return su == mapping.pk;
|
||||||
"goauthentik.io/sources/ldap/default-name";
|
});
|
||||||
} else {
|
}
|
||||||
selected = Array.from(
|
return html`<option
|
||||||
this.instance?.propertyMappingsGroup,
|
value=${ifDefined(mapping.pk)}
|
||||||
).some((su) => {
|
?selected=${selected}
|
||||||
return su == mapping.pk;
|
>
|
||||||
});
|
${mapping.name}
|
||||||
}
|
</option>`;
|
||||||
return html`<option
|
})}
|
||||||
value=${ifDefined(mapping.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${mapping.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Property mappings used to group creation.`}
|
${t`Property mappings used to group creation.`}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
|
import { rootInterface } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
@ -13,7 +14,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CapabilitiesEnum,
|
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.`}
|
${t`Path template for users created. Use placeholders like \`%(slug)s\` to insert the source slug.`}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${until(
|
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
|
||||||
config().then((c) => {
|
? html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
<input type="file" value="" class="pf-c-form-control" />
|
||||||
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
${this.instance?.icon
|
||||||
<input type="file" value="" class="pf-c-form-control" />
|
? html`
|
||||||
${this.instance?.icon
|
<p class="pf-c-form__helper-text">
|
||||||
? html`
|
${t`Currently set to:`} ${this.instance?.icon}
|
||||||
<p class="pf-c-form__helper-text">
|
</p>
|
||||||
${t`Currently set to:`} ${this.instance?.icon}
|
`
|
||||||
</p>
|
: html``}
|
||||||
`
|
</ak-form-element-horizontal>
|
||||||
: html``}
|
${this.instance?.icon
|
||||||
</ak-form-element-horizontal>
|
? html`
|
||||||
${this.instance?.icon
|
<ak-form-element-horizontal>
|
||||||
? html`
|
<label class="pf-c-switch">
|
||||||
<ak-form-element-horizontal>
|
<input
|
||||||
<label class="pf-c-switch">
|
class="pf-c-switch__input"
|
||||||
<input
|
type="checkbox"
|
||||||
class="pf-c-switch__input"
|
@change=${(ev: Event) => {
|
||||||
type="checkbox"
|
const target = ev.target as HTMLInputElement;
|
||||||
@change=${(ev: Event) => {
|
this.clearIcon = target.checked;
|
||||||
const target = ev.target as HTMLInputElement;
|
}}
|
||||||
this.clearIcon = target.checked;
|
/>
|
||||||
}}
|
<span class="pf-c-switch__toggle">
|
||||||
/>
|
<span class="pf-c-switch__toggle-icon">
|
||||||
<span class="pf-c-switch__toggle">
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
<span class="pf-c-switch__toggle-icon">
|
</span>
|
||||||
<i
|
</span>
|
||||||
class="fas fa-check"
|
<span class="pf-c-switch__label"> ${t`Clear icon`} </span>
|
||||||
aria-hidden="true"
|
</label>
|
||||||
></i>
|
<p class="pf-c-form__helper-text">
|
||||||
</span>
|
${t`Delete currently set icon.`}
|
||||||
</span>
|
</p>
|
||||||
<span class="pf-c-switch__label">
|
</ak-form-element-horizontal>
|
||||||
${t`Clear icon`}
|
`
|
||||||
</span>
|
: html``}`
|
||||||
</label>
|
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
|
<input
|
||||||
<p class="pf-c-form__helper-text">
|
type="text"
|
||||||
${t`Delete currently set icon.`}
|
value="${first(this.instance?.icon, "")}"
|
||||||
</p>
|
class="pf-c-form-control"
|
||||||
</ak-form-element-horizontal>
|
/>
|
||||||
`
|
<p class="pf-c-form__helper-text">
|
||||||
: html``}`;
|
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
|
||||||
}
|
</p>
|
||||||
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
</ak-form-element-horizontal>`}
|
||||||
<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}>
|
<ak-form-group .expanded=${true}>
|
||||||
<span slot="header"> ${t`Protocol settings`} </span>
|
<span slot="header"> ${t`Protocol settings`} </span>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
|
import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
|
||||||
import { first, randomString } from "@goauthentik/common/utils";
|
import { first, randomString } from "@goauthentik/common/utils";
|
||||||
|
import { rootInterface } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
@ -13,7 +14,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CapabilitiesEnum,
|
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.`}
|
${t`Path template for users created. Use placeholders like \`%(slug)s\` to insert the source slug.`}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${until(
|
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
|
||||||
config().then((c) => {
|
? html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
<input type="file" value="" class="pf-c-form-control" />
|
||||||
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
${this.instance?.icon
|
||||||
<input type="file" value="" class="pf-c-form-control" />
|
? html`
|
||||||
${this.instance?.icon
|
<p class="pf-c-form__helper-text">
|
||||||
? html`
|
${t`Currently set to:`} ${this.instance?.icon}
|
||||||
<p class="pf-c-form__helper-text">
|
</p>
|
||||||
${t`Currently set to:`} ${this.instance?.icon}
|
`
|
||||||
</p>
|
: html``}
|
||||||
`
|
</ak-form-element-horizontal>
|
||||||
: html``}
|
${this.instance?.icon
|
||||||
</ak-form-element-horizontal>
|
? html`
|
||||||
${this.instance?.icon
|
<ak-form-element-horizontal>
|
||||||
? html`
|
<label class="pf-c-switch">
|
||||||
<ak-form-element-horizontal>
|
<input
|
||||||
<label class="pf-c-switch">
|
class="pf-c-switch__input"
|
||||||
<input
|
type="checkbox"
|
||||||
class="pf-c-switch__input"
|
@change=${(ev: Event) => {
|
||||||
type="checkbox"
|
const target = ev.target as HTMLInputElement;
|
||||||
@change=${(ev: Event) => {
|
this.clearIcon = target.checked;
|
||||||
const target = ev.target as HTMLInputElement;
|
}}
|
||||||
this.clearIcon = target.checked;
|
/>
|
||||||
}}
|
<span class="pf-c-switch__toggle">
|
||||||
/>
|
<span class="pf-c-switch__toggle-icon">
|
||||||
<span class="pf-c-switch__toggle">
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
<span class="pf-c-switch__toggle-icon">
|
</span>
|
||||||
<i
|
</span>
|
||||||
class="fas fa-check"
|
<span class="pf-c-switch__label"> ${t`Clear icon`} </span>
|
||||||
aria-hidden="true"
|
</label>
|
||||||
></i>
|
<p class="pf-c-form__helper-text">
|
||||||
</span>
|
${t`Delete currently set icon.`}
|
||||||
</span>
|
</p>
|
||||||
<span class="pf-c-switch__label">
|
</ak-form-element-horizontal>
|
||||||
${t`Clear icon`}
|
`
|
||||||
</span>
|
: html``}`
|
||||||
</label>
|
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
<p class="pf-c-form__helper-text">
|
<input
|
||||||
${t`Delete currently set icon.`}
|
type="text"
|
||||||
</p>
|
value="${first(this.instance?.icon, "")}"
|
||||||
</ak-form-element-horizontal>
|
class="pf-c-form-control"
|
||||||
`
|
/>
|
||||||
: html``}`;
|
<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".`}
|
||||||
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
</p>
|
||||||
<input
|
</ak-form-element-horizontal>`}
|
||||||
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}>
|
<ak-form-group .expanded=${true}>
|
||||||
<span slot="header"> ${t`Protocol settings`} </span>
|
<span slot="header"> ${t`Protocol settings`} </span>
|
||||||
<div slot="body" class="pf-c-form">
|
<div slot="body" class="pf-c-form">
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
|
import { rootInterface } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
@ -13,7 +14,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BindingTypeEnum,
|
BindingTypeEnum,
|
||||||
|
@ -161,62 +161,52 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${until(
|
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.SaveMedia)
|
||||||
config().then((c) => {
|
? html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
<input type="file" value="" class="pf-c-form-control" />
|
||||||
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
${this.instance?.icon
|
||||||
<input type="file" value="" class="pf-c-form-control" />
|
? html`
|
||||||
${this.instance?.icon
|
<p class="pf-c-form__helper-text">
|
||||||
? html`
|
${t`Currently set to:`} ${this.instance?.icon}
|
||||||
<p class="pf-c-form__helper-text">
|
</p>
|
||||||
${t`Currently set to:`} ${this.instance?.icon}
|
`
|
||||||
</p>
|
: html``}
|
||||||
`
|
</ak-form-element-horizontal>
|
||||||
: html``}
|
${this.instance?.icon
|
||||||
</ak-form-element-horizontal>
|
? html`
|
||||||
${this.instance?.icon
|
<ak-form-element-horizontal>
|
||||||
? html`
|
<label class="pf-c-switch">
|
||||||
<ak-form-element-horizontal>
|
<input
|
||||||
<label class="pf-c-switch">
|
class="pf-c-switch__input"
|
||||||
<input
|
type="checkbox"
|
||||||
class="pf-c-switch__input"
|
@change=${(ev: Event) => {
|
||||||
type="checkbox"
|
const target = ev.target as HTMLInputElement;
|
||||||
@change=${(ev: Event) => {
|
this.clearIcon = target.checked;
|
||||||
const target = ev.target as HTMLInputElement;
|
}}
|
||||||
this.clearIcon = target.checked;
|
/>
|
||||||
}}
|
<span class="pf-c-switch__toggle">
|
||||||
/>
|
<span class="pf-c-switch__toggle-icon">
|
||||||
<span class="pf-c-switch__toggle">
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
<span class="pf-c-switch__toggle-icon">
|
</span>
|
||||||
<i
|
</span>
|
||||||
class="fas fa-check"
|
<span class="pf-c-switch__label"> ${t`Clear icon`} </span>
|
||||||
aria-hidden="true"
|
</label>
|
||||||
></i>
|
<p class="pf-c-form__helper-text">
|
||||||
</span>
|
${t`Delete currently set icon.`}
|
||||||
</span>
|
</p>
|
||||||
<span class="pf-c-switch__label">
|
</ak-form-element-horizontal>
|
||||||
${t`Clear icon`}
|
`
|
||||||
</span>
|
: html``}`
|
||||||
</label>
|
: html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
<p class="pf-c-form__helper-text">
|
<input
|
||||||
${t`Delete currently set icon.`}
|
type="text"
|
||||||
</p>
|
value="${first(this.instance?.icon, "")}"
|
||||||
</ak-form-element-horizontal>
|
class="pf-c-form-control"
|
||||||
`
|
/>
|
||||||
: html``}`;
|
<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".`}
|
||||||
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
</p>
|
||||||
<input
|
</ak-form-element-horizontal>`}
|
||||||
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}>
|
<ak-form-group .expanded=${true}>
|
||||||
<span slot="header"> ${t`Protocol settings`} </span>
|
<span slot="header"> ${t`Protocol settings`} </span>
|
||||||
|
|
|
@ -10,30 +10,35 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthenticatorValidateStage,
|
AuthenticatorValidateStage,
|
||||||
DeviceClassesEnum,
|
DeviceClassesEnum,
|
||||||
NotConfiguredActionEnum,
|
NotConfiguredActionEnum,
|
||||||
|
PaginatedStageList,
|
||||||
StagesApi,
|
StagesApi,
|
||||||
UserVerificationEnum,
|
UserVerificationEnum,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-stage-authenticator-validate-form")
|
@customElement("ak-stage-authenticator-validate-form")
|
||||||
export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValidateStage, string> {
|
export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValidateStage, string> {
|
||||||
loadInstance(pk: string): Promise<AuthenticatorValidateStage> {
|
async loadInstance(pk: string): Promise<AuthenticatorValidateStage> {
|
||||||
return new StagesApi(DEFAULT_CONFIG)
|
const stage = await new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorValidateRetrieve({
|
||||||
.stagesAuthenticatorValidateRetrieve({
|
stageUuid: pk,
|
||||||
stageUuid: pk,
|
});
|
||||||
})
|
this.showConfigurationStages =
|
||||||
.then((stage) => {
|
stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
|
||||||
this.showConfigurationStages =
|
return stage;
|
||||||
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 })
|
@property({ type: Boolean })
|
||||||
showConfigurationStages = true;
|
showConfigurationStages = true;
|
||||||
|
|
||||||
|
@ -216,28 +221,19 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
|
||||||
name="configurationStages"
|
name="configurationStages"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.stages?.results.map((stage) => {
|
||||||
new StagesApi(DEFAULT_CONFIG)
|
const selected = Array.from(
|
||||||
.stagesAllList({
|
this.instance?.configurationStages || [],
|
||||||
ordering: "name",
|
).some((su) => {
|
||||||
})
|
return su == stage.pk;
|
||||||
.then((stages) => {
|
});
|
||||||
return stages.results.map((stage) => {
|
return html`<option
|
||||||
const selected = Array.from(
|
value=${ifDefined(stage.pk)}
|
||||||
this.instance?.configurationStages || [],
|
?selected=${selected}
|
||||||
).some((su) => {
|
>
|
||||||
return su == stage.pk;
|
${stage.name} (${stage.verboseName})
|
||||||
});
|
</option>`;
|
||||||
return html`<option
|
})}
|
||||||
value=${ifDefined(stage.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${stage.name} (${stage.verboseName})
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<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.`}
|
${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.`}
|
||||||
|
|
|
@ -9,23 +9,25 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.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")
|
@customElement("ak-stage-email-form")
|
||||||
export class EmailStageForm extends ModelForm<EmailStage, string> {
|
export class EmailStageForm extends ModelForm<EmailStage, string> {
|
||||||
loadInstance(pk: string): Promise<EmailStage> {
|
async loadInstance(pk: string): Promise<EmailStage> {
|
||||||
return new StagesApi(DEFAULT_CONFIG)
|
const stage = await new StagesApi(DEFAULT_CONFIG).stagesEmailRetrieve({
|
||||||
.stagesEmailRetrieve({
|
stageUuid: pk,
|
||||||
stageUuid: pk,
|
});
|
||||||
})
|
this.showConnectionSettings = !stage.useGlobalSettings;
|
||||||
.then((stage) => {
|
return stage;
|
||||||
this.showConnectionSettings = !stage.useGlobalSettings;
|
|
||||||
return stage;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async load(): Promise<void> {
|
||||||
|
this.templates = await new StagesApi(DEFAULT_CONFIG).stagesEmailTemplatesList();
|
||||||
|
}
|
||||||
|
|
||||||
|
templates?: TypeCreate[];
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
showConnectionSettings = false;
|
showConnectionSettings = false;
|
||||||
|
|
||||||
|
@ -232,23 +234,15 @@ export class EmailStageForm extends ModelForm<EmailStage, string> {
|
||||||
name="template"
|
name="template"
|
||||||
>
|
>
|
||||||
<select name="users" class="pf-c-form-control">
|
<select name="users" class="pf-c-form-control">
|
||||||
${until(
|
${this.templates?.map((template) => {
|
||||||
new StagesApi(DEFAULT_CONFIG)
|
const selected = this.instance?.template === template.name;
|
||||||
.stagesEmailTemplatesList()
|
return html`<option
|
||||||
.then((templates) => {
|
value=${ifDefined(template.name)}
|
||||||
return templates.map((template) => {
|
?selected=${selected}
|
||||||
const selected =
|
>
|
||||||
this.instance?.template === template.name;
|
${template.description}
|
||||||
return html`<option
|
</option>`;
|
||||||
value=${ifDefined(template.name)}
|
})}
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${template.description}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Flow,
|
Flow,
|
||||||
|
@ -19,6 +18,7 @@ import {
|
||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
FlowsInstancesListRequest,
|
FlowsInstancesListRequest,
|
||||||
IdentificationStage,
|
IdentificationStage,
|
||||||
|
PaginatedSourceList,
|
||||||
SourcesApi,
|
SourcesApi,
|
||||||
Stage,
|
Stage,
|
||||||
StagesApi,
|
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 {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated stage.`;
|
return t`Successfully updated stage.`;
|
||||||
|
@ -80,7 +88,7 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
|
||||||
<span slot="header"> ${t`Stage-specific settings`} </span>
|
<span slot="header"> ${t`Stage-specific settings`} </span>
|
||||||
<div slot="body" class="pf-c-form">
|
<div slot="body" class="pf-c-form">
|
||||||
<ak-form-element-horizontal label=${t`User fields`} name="userFields">
|
<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
|
<option
|
||||||
value=${UserFieldsEnum.Username}
|
value=${UserFieldsEnum.Username}
|
||||||
?selected=${this.isUserFieldSelected(UserFieldsEnum.Username)}
|
?selected=${this.isUserFieldSelected(UserFieldsEnum.Username)}
|
||||||
|
@ -187,35 +195,28 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
|
||||||
name="sources"
|
name="sources"
|
||||||
>
|
>
|
||||||
<select class="pf-c-form-control" multiple>
|
<select class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.sources?.results.map((source) => {
|
||||||
new SourcesApi(DEFAULT_CONFIG)
|
let selected = Array.from(this.instance?.sources || []).some(
|
||||||
.sourcesAllList({})
|
(su) => {
|
||||||
.then((sources) => {
|
return su == source.pk;
|
||||||
return sources.results.map((source) => {
|
},
|
||||||
let selected = Array.from(
|
);
|
||||||
this.instance?.sources || [],
|
// Creating a new instance, auto-select built-in source
|
||||||
).some((su) => {
|
// Only when no other sources exist
|
||||||
return su == source.pk;
|
if (
|
||||||
});
|
!this.instance &&
|
||||||
// Creating a new instance, auto-select built-in source
|
source.component === "" &&
|
||||||
// Only when no other sources exist
|
(this.sources?.results || []).length < 2
|
||||||
if (
|
) {
|
||||||
!this.instance &&
|
selected = true;
|
||||||
source.component === "" &&
|
}
|
||||||
sources.results.length < 2
|
return html`<option
|
||||||
) {
|
value=${ifDefined(source.pk)}
|
||||||
selected = true;
|
?selected=${selected}
|
||||||
}
|
>
|
||||||
return html`<option
|
${source.name}
|
||||||
value=${ifDefined(source.pk)}
|
</option>`;
|
||||||
?selected=${selected}
|
})}
|
||||||
>
|
|
||||||
${source.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<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.`}
|
${t`Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP.`}
|
||||||
|
|
|
@ -10,9 +10,14 @@ import { t } from "@lingui/macro";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.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")
|
@customElement("ak-stage-prompt-form")
|
||||||
export class PromptStageForm extends ModelForm<PromptStage, string> {
|
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 {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated stage.`;
|
return t`Successfully updated stage.`;
|
||||||
|
@ -61,28 +78,19 @@ export class PromptStageForm extends ModelForm<PromptStage, string> {
|
||||||
<div slot="body" class="pf-c-form">
|
<div slot="body" class="pf-c-form">
|
||||||
<ak-form-element-horizontal label=${t`Fields`} ?required=${true} name="fields">
|
<ak-form-element-horizontal label=${t`Fields`} ?required=${true} name="fields">
|
||||||
<select name="users" class="pf-c-form-control" multiple>
|
<select name="users" class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.prompts?.results.map((prompt) => {
|
||||||
new StagesApi(DEFAULT_CONFIG)
|
const selected = Array.from(this.instance?.fields || []).some(
|
||||||
.stagesPromptPromptsList({
|
(su) => {
|
||||||
ordering: "field_name",
|
return su == prompt.pk;
|
||||||
})
|
},
|
||||||
.then((prompts) => {
|
);
|
||||||
return prompts.results.map((prompt) => {
|
return html`<option
|
||||||
const selected = Array.from(
|
value=${ifDefined(prompt.pk)}
|
||||||
this.instance?.fields || [],
|
?selected=${selected}
|
||||||
).some((su) => {
|
>
|
||||||
return su == prompt.pk;
|
${t`${prompt.name} ("${prompt.fieldKey}", of type ${prompt.type})`}
|
||||||
});
|
</option>`;
|
||||||
return html`<option
|
})}
|
||||||
value=${ifDefined(prompt.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${t`${prompt.name} ("${prompt.fieldKey}", of type ${prompt.type})`}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Hold control/command to select multiple items.`}
|
${t`Hold control/command to select multiple items.`}
|
||||||
|
@ -101,28 +109,19 @@ export class PromptStageForm extends ModelForm<PromptStage, string> {
|
||||||
name="validationPolicies"
|
name="validationPolicies"
|
||||||
>
|
>
|
||||||
<select name="users" class="pf-c-form-control" multiple>
|
<select name="users" class="pf-c-form-control" multiple>
|
||||||
${until(
|
${this.policies?.results.map((policy) => {
|
||||||
new PoliciesApi(DEFAULT_CONFIG)
|
const selected = Array.from(
|
||||||
.policiesAllList({
|
this.instance?.validationPolicies || [],
|
||||||
ordering: "name",
|
).some((su) => {
|
||||||
})
|
return su == policy.pk;
|
||||||
.then((policies) => {
|
});
|
||||||
return policies.results.map((policy) => {
|
return html`<option
|
||||||
const selected = Array.from(
|
value=${ifDefined(policy.pk)}
|
||||||
this.instance?.validationPolicies || [],
|
?selected=${selected}
|
||||||
).some((su) => {
|
>
|
||||||
return su == policy.pk;
|
${t`${policy.name} (${policy.verboseName})`}
|
||||||
});
|
</option>`;
|
||||||
return html`<option
|
})}
|
||||||
value=${ifDefined(policy.pk)}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${t`${policy.name} (${policy.verboseName})`}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
</select>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Selected policies are executed when the stage is submitted to validate the data.`}
|
${t`Selected policies are executed when the stage is submitted to validate the data.`}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { tenant } from "@goauthentik/common/api/config";
|
import { config, tenant } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_LOCALE_CHANGE, EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
|
import { EVENT_LOCALE_CHANGE, EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
|
||||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
|
||||||
|
|
||||||
import { LitElement } from "lit";
|
import { LitElement } from "lit";
|
||||||
import { state } from "lit/decorators.js";
|
import { state } from "lit/decorators.js";
|
||||||
|
@ -9,13 +9,13 @@ import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||||
import ThemeDark from "@goauthentik/common/styles/theme-dark.css";
|
import ThemeDark from "@goauthentik/common/styles/theme-dark.css";
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
import { CurrentTenant, UiThemeEnum } from "@goauthentik/api";
|
import { Config, CurrentTenant, UiThemeEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
export function rootInterface(): Interface | undefined {
|
export function rootInterface<T extends Interface>(): T | undefined {
|
||||||
const el = Array.from(document.body.querySelectorAll("*")).filter(
|
const el = Array.from(document.body.querySelectorAll("*")).filter(
|
||||||
(el) => el instanceof Interface,
|
(el) => el instanceof Interface,
|
||||||
);
|
);
|
||||||
return el[0] as Interface;
|
return el[0] as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
let css: Promise<string[]> | undefined;
|
let css: Promise<string[]> | undefined;
|
||||||
|
@ -171,10 +171,17 @@ export class Interface extends AKElement {
|
||||||
@state()
|
@state()
|
||||||
tenant?: CurrentTenant;
|
tenant?: CurrentTenant;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
uiConfig?: UIConfig;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
config?: Config;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, PFBase];
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, PFBase];
|
||||||
tenant().then((tenant) => (this.tenant = tenant));
|
tenant().then((tenant) => (this.tenant = tenant));
|
||||||
|
config().then((config) => (this.config = config));
|
||||||
}
|
}
|
||||||
|
|
||||||
_activateTheme(root: AdoptedStyleSheetsElement, theme: UiThemeEnum): void {
|
_activateTheme(root: AdoptedStyleSheetsElement, theme: UiThemeEnum): void {
|
||||||
|
@ -183,7 +190,9 @@ export class Interface extends AKElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTheme(): Promise<UiThemeEnum> {
|
async getTheme(): Promise<UiThemeEnum> {
|
||||||
const config = await uiConfig();
|
if (!this.uiConfig) {
|
||||||
return config.theme?.base || UiThemeEnum.Automatic;
|
this.uiConfig = await uiConfig();
|
||||||
|
}
|
||||||
|
return this.uiConfig.theme?.base || UiThemeEnum.Automatic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
|
||||||
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
|
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
|
||||||
|
|
||||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
|
@ -22,7 +22,7 @@ import { ResponseError, ValidationError } from "@goauthentik/api";
|
||||||
|
|
||||||
export class PreventFormSubmit {
|
export class PreventFormSubmit {
|
||||||
// Stub class which can be returned by form elements to prevent the form from submitting
|
// Stub class which can be returned by form elements to prevent the form from submitting
|
||||||
constructor(public message: string) {}
|
constructor(public message: string, public element?: HorizontalFormElement) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class APIError extends Error {
|
export class APIError extends Error {
|
||||||
|
@ -36,16 +36,15 @@ export interface KeyUnknown {
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ak-form")
|
@customElement("ak-form")
|
||||||
export class Form<T> extends AKElement {
|
export abstract class Form<T> extends AKElement {
|
||||||
|
abstract send(data: T): Promise<unknown>;
|
||||||
|
|
||||||
viewportCheck = true;
|
viewportCheck = true;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
successMessage = "";
|
successMessage = "";
|
||||||
|
|
||||||
@property()
|
@state()
|
||||||
send!: (data: T) => Promise<unknown>;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
nonFieldErrors?: string[];
|
nonFieldErrors?: string[];
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
|
@ -177,17 +176,15 @@ export class Form<T> extends AKElement {
|
||||||
json[element.name] = inputElement.checked;
|
json[element.name] = inputElement.checked;
|
||||||
} else if (inputElement.tagName.toLowerCase() === "ak-search-select") {
|
} else if (inputElement.tagName.toLowerCase() === "ak-search-select") {
|
||||||
const select = inputElement as unknown as SearchSelect<unknown>;
|
const select = inputElement as unknown as SearchSelect<unknown>;
|
||||||
let value: unknown;
|
|
||||||
try {
|
try {
|
||||||
value = select.toForm();
|
const value = select.toForm();
|
||||||
} catch {
|
json[element.name] = value;
|
||||||
console.debug("authentik/form: SearchSelect.value error");
|
} catch (exc) {
|
||||||
return;
|
if (exc instanceof PreventFormSubmit) {
|
||||||
|
throw new PreventFormSubmit(exc.message, element);
|
||||||
|
}
|
||||||
|
throw exc;
|
||||||
}
|
}
|
||||||
if (value instanceof PreventFormSubmit) {
|
|
||||||
throw new Error(value.message);
|
|
||||||
}
|
|
||||||
json[element.name] = value;
|
|
||||||
} else {
|
} else {
|
||||||
this.serializeFieldRecursive(inputElement, inputElement.value, json);
|
this.serializeFieldRecursive(inputElement, inputElement.value, json);
|
||||||
}
|
}
|
||||||
|
@ -215,30 +212,27 @@ export class Form<T> extends AKElement {
|
||||||
parent[nameElements[nameElements.length - 1]] = value;
|
parent[nameElements[nameElements.length - 1]] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(ev: Event): Promise<unknown> | undefined {
|
async submit(ev: Event): Promise<unknown | undefined> {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const data = this.serializeForm();
|
try {
|
||||||
if (!data) {
|
const data = this.serializeForm();
|
||||||
return;
|
if (!data) {
|
||||||
}
|
return;
|
||||||
return this.send(data)
|
}
|
||||||
.then((r) => {
|
const response = await this.send(data);
|
||||||
showMessage({
|
showMessage({
|
||||||
level: MessageLevel.success,
|
level: MessageLevel.success,
|
||||||
message: this.getSuccessMessage(),
|
message: this.getSuccessMessage(),
|
||||||
});
|
});
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent(EVENT_REFRESH, {
|
new CustomEvent(EVENT_REFRESH, {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
composed: true,
|
composed: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return r;
|
return response;
|
||||||
})
|
} catch (ex) {
|
||||||
.catch(async (ex: Error | ResponseError) => {
|
if (ex instanceof ResponseError) {
|
||||||
if (!(ex instanceof ResponseError)) {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
let msg = ex.response.statusText;
|
let msg = ex.response.statusText;
|
||||||
if (ex.response.status > 399 && ex.response.status < 500) {
|
if (ex.response.status > 399 && ex.response.status < 500) {
|
||||||
const errorMessage: ValidationError = await ex.response.json();
|
const errorMessage: ValidationError = await ex.response.json();
|
||||||
|
@ -277,9 +271,14 @@ export class Form<T> extends AKElement {
|
||||||
message: msg,
|
message: msg,
|
||||||
level: MessageLevel.error,
|
level: MessageLevel.error,
|
||||||
});
|
});
|
||||||
// rethrow the error so the form doesn't close
|
}
|
||||||
throw ex;
|
if (ex instanceof PreventFormSubmit && ex.element) {
|
||||||
});
|
ex.element.errorMessages = [ex.message];
|
||||||
|
ex.element.invalid = true;
|
||||||
|
}
|
||||||
|
// rethrow the error so the form doesn't close
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderForm(): TemplateResult {
|
renderForm(): TemplateResult {
|
||||||
|
|
|
@ -1,27 +1,44 @@
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
|
import "@goauthentik/elements/EmptyState";
|
||||||
import { Form } from "@goauthentik/elements/forms/Form";
|
import { Form } from "@goauthentik/elements/forms/Form";
|
||||||
|
|
||||||
import { TemplateResult } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { property } from "lit/decorators.js";
|
import { property } from "lit/decorators.js";
|
||||||
|
|
||||||
export abstract class ModelForm<T, PKT extends string | number> extends Form<T> {
|
export abstract class ModelForm<T, PKT extends string | number> extends Form<T> {
|
||||||
abstract loadInstance(pk: PKT): Promise<T>;
|
abstract loadInstance(pk: PKT): Promise<T>;
|
||||||
|
|
||||||
|
async load(): Promise<void> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
set instancePk(value: PKT) {
|
set instancePk(value: PKT) {
|
||||||
this._instancePk = value;
|
this._instancePk = value;
|
||||||
if (this.viewportCheck && !this.isInViewport) {
|
if (this.viewportCheck && !this.isInViewport) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loadInstance(value).then((instance) => {
|
if (this._isLoading) {
|
||||||
this.instance = instance;
|
return;
|
||||||
this.requestUpdate();
|
}
|
||||||
|
this._isLoading = true;
|
||||||
|
this.load().then(() => {
|
||||||
|
this.loadInstance(value).then((instance) => {
|
||||||
|
this.instance = instance;
|
||||||
|
this._isLoading = false;
|
||||||
|
this.requestUpdate();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _instancePk?: PKT;
|
private _instancePk?: PKT;
|
||||||
|
|
||||||
|
// Keep track if we've loaded the model instance
|
||||||
private _initialLoad = false;
|
private _initialLoad = false;
|
||||||
|
// Keep track if we've done the general data loading of load()
|
||||||
|
private _initialDataLoad = false;
|
||||||
|
|
||||||
|
private _isLoading = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
instance?: T = this.defaultInstance;
|
instance?: T = this.defaultInstance;
|
||||||
|
@ -45,17 +62,29 @@ export abstract class ModelForm<T, PKT extends string | number> extends Form<T>
|
||||||
this._initialLoad = false;
|
this._initialLoad = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderVisible(): TemplateResult {
|
||||||
|
if ((this._instancePk && !this.instance) || !this._initialDataLoad) {
|
||||||
|
return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
|
||||||
|
}
|
||||||
|
return super.renderVisible();
|
||||||
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (this._instancePk && !this._initialLoad) {
|
// if we're in viewport now and haven't loaded AND have a PK set, load now
|
||||||
if (
|
// Or if we don't check for viewport in some cases
|
||||||
// if we're in viewport now and haven't loaded AND have a PK set, load now
|
const viewportVisible = this.isInViewport || !this.viewportCheck;
|
||||||
this.isInViewport ||
|
if (this._instancePk && !this._initialLoad && viewportVisible) {
|
||||||
// Or if we don't check for viewport in some cases
|
this.instancePk = this._instancePk;
|
||||||
!this.viewportCheck
|
this._initialLoad = true;
|
||||||
) {
|
} else if (!this._initialDataLoad && viewportVisible) {
|
||||||
this.instancePk = this._instancePk;
|
// else if since if the above case triggered that will also call this.load(), so
|
||||||
this._initialLoad = true;
|
// ensure we don't load again
|
||||||
}
|
this.load().then(() => {
|
||||||
|
this._initialDataLoad = true;
|
||||||
|
// Class attributes changed in this.load() might not be @property()
|
||||||
|
// or @state() so let's trigger a re-render to be sure we get updated
|
||||||
|
this.requestUpdate();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return super.render();
|
return super.render();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
@customElement("ak-proxy-form")
|
@customElement("ak-proxy-form")
|
||||||
export class ProxyForm extends Form<unknown> {
|
export abstract class ProxyForm extends Form<unknown> {
|
||||||
@property()
|
@property()
|
||||||
type!: string;
|
type!: string;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export class ProxyForm extends Form<unknown> {
|
||||||
|
|
||||||
innerElement?: Form<unknown>;
|
innerElement?: Form<unknown>;
|
||||||
|
|
||||||
submit(ev: Event): Promise<unknown> | undefined {
|
async submit(ev: Event): Promise<unknown | undefined> {
|
||||||
return this.innerElement?.submit(ev);
|
return this.innerElement?.submit(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,9 @@ export class ProxyForm extends Form<unknown> {
|
||||||
if (this.type in this.typeMap) {
|
if (this.type in this.typeMap) {
|
||||||
elementName = this.typeMap[this.type];
|
elementName = this.typeMap[this.type];
|
||||||
}
|
}
|
||||||
this.innerElement = document.createElement(elementName) as Form<unknown>;
|
if (!this.innerElement) {
|
||||||
|
this.innerElement = document.createElement(elementName) as Form<unknown>;
|
||||||
|
}
|
||||||
this.innerElement.viewportCheck = this.viewportCheck;
|
this.innerElement.viewportCheck = this.viewportCheck;
|
||||||
for (const k in this.args) {
|
for (const k in this.args) {
|
||||||
this.innerElement.setAttribute(k, this.args[k] as string);
|
this.innerElement.setAttribute(k, this.args[k] as string);
|
||||||
|
|
|
@ -25,11 +25,6 @@ export class Radio<T> extends AKElement {
|
||||||
@property()
|
@property()
|
||||||
value?: T;
|
value?: T;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
onChange: (value: T) => void = () => {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
|
@ -63,7 +58,13 @@ export class Radio<T> extends AKElement {
|
||||||
id=${elId}
|
id=${elId}
|
||||||
@change=${() => {
|
@change=${() => {
|
||||||
this.value = opt.value;
|
this.value = opt.value;
|
||||||
this.onChange(opt.value);
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("change", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: opt.value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
.checked=${opt.value === this.value}
|
.checked=${opt.value === this.value}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -94,7 +94,7 @@ export class SearchSelect<T> extends AKElement {
|
||||||
|
|
||||||
toForm(): unknown {
|
toForm(): unknown {
|
||||||
if (!this.objects) {
|
if (!this.objects) {
|
||||||
return new PreventFormSubmit(t`Loading options...`);
|
throw new PreventFormSubmit(t`Loading options...`);
|
||||||
}
|
}
|
||||||
return this.value(this.selectedObject) || "";
|
return this.value(this.selectedObject) || "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,13 @@ import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-gro
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
@customElement("ak-wizard-form")
|
@customElement("ak-wizard-form")
|
||||||
export class WizardForm extends Form<KeyUnknown> {
|
export abstract class WizardForm extends Form<KeyUnknown> {
|
||||||
viewportCheck = false;
|
viewportCheck = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
nextDataCallback!: (data: KeyUnknown) => Promise<boolean>;
|
nextDataCallback!: (data: KeyUnknown) => Promise<boolean>;
|
||||||
|
|
||||||
submit(): Promise<boolean> | undefined {
|
async submit(): Promise<boolean | undefined> {
|
||||||
const data = this.serializeForm();
|
const data = this.serializeForm();
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
EVENT_WS_MESSAGE,
|
EVENT_WS_MESSAGE,
|
||||||
} from "@goauthentik/common/constants";
|
} from "@goauthentik/common/constants";
|
||||||
import { configureSentry } from "@goauthentik/common/sentry";
|
import { configureSentry } from "@goauthentik/common/sentry";
|
||||||
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
|
import { UserDisplay } from "@goauthentik/common/ui/config";
|
||||||
import { autoDetectLanguage } from "@goauthentik/common/ui/locale";
|
import { autoDetectLanguage } from "@goauthentik/common/ui/locale";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
|
@ -56,9 +56,6 @@ export class UserInterface extends Interface {
|
||||||
@state()
|
@state()
|
||||||
me?: SessionUser;
|
me?: SessionUser;
|
||||||
|
|
||||||
@state()
|
|
||||||
config?: UIConfig;
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
|
@ -126,7 +123,6 @@ export class UserInterface extends Interface {
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
async firstUpdated(): Promise<void> {
|
||||||
this.me = await me();
|
this.me = await me();
|
||||||
this.config = await uiConfig();
|
|
||||||
const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
|
const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
|
||||||
seen: false,
|
seen: false,
|
||||||
ordering: "-created",
|
ordering: "-created",
|
||||||
|
@ -137,11 +133,11 @@ export class UserInterface extends Interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.config || !this.me) {
|
if (!this.uiConfig || !this.me) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
let userDisplay = "";
|
let userDisplay = "";
|
||||||
switch (this.config.navbar.userDisplay) {
|
switch (this.uiConfig.navbar.userDisplay) {
|
||||||
case UserDisplay.username:
|
case UserDisplay.username:
|
||||||
userDisplay = this.me.user.username;
|
userDisplay = this.me.user.username;
|
||||||
break;
|
break;
|
||||||
|
@ -155,7 +151,7 @@ export class UserInterface extends Interface {
|
||||||
userDisplay = this.me.user.username;
|
userDisplay = this.me.user.username;
|
||||||
}
|
}
|
||||||
return html`<div class="pf-c-page">
|
return html`<div class="pf-c-page">
|
||||||
<div class="background-wrapper" style="${this.config.theme.background}"></div>
|
<div class="background-wrapper" style="${this.uiConfig.theme.background}"></div>
|
||||||
<header class="pf-c-page__header">
|
<header class="pf-c-page__header">
|
||||||
<div class="pf-c-page__header-brand">
|
<div class="pf-c-page__header-brand">
|
||||||
<a href="#/" class="pf-c-page__header-brand-link">
|
<a href="#/" class="pf-c-page__header-brand-link">
|
||||||
|
@ -168,7 +164,7 @@ export class UserInterface extends Interface {
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-page__header-tools">
|
<div class="pf-c-page__header-tools">
|
||||||
<div class="pf-c-page__header-tools-group">
|
<div class="pf-c-page__header-tools-group">
|
||||||
${this.config.enabledFeatures.apiDrawer
|
${this.uiConfig.enabledFeatures.apiDrawer
|
||||||
? html`<div
|
? html`<div
|
||||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
||||||
>
|
>
|
||||||
|
@ -186,7 +182,7 @@ export class UserInterface extends Interface {
|
||||||
</button>
|
</button>
|
||||||
</div>`
|
</div>`
|
||||||
: html``}
|
: html``}
|
||||||
${this.config.enabledFeatures.notificationDrawer
|
${this.uiConfig.enabledFeatures.notificationDrawer
|
||||||
? html`<div
|
? html`<div
|
||||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
||||||
>
|
>
|
||||||
|
@ -216,7 +212,7 @@ export class UserInterface extends Interface {
|
||||||
</button>
|
</button>
|
||||||
</div> `
|
</div> `
|
||||||
: html``}
|
: html``}
|
||||||
${this.config.enabledFeatures.settings
|
${this.uiConfig.enabledFeatures.settings
|
||||||
? html` <div class="pf-c-page__header-tools-item">
|
? html` <div class="pf-c-page__header-tools-item">
|
||||||
<a class="pf-c-button pf-m-plain" type="button" href="#/settings">
|
<a class="pf-c-button pf-m-plain" type="button" href="#/settings">
|
||||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||||
|
|
Reference in New Issue