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