web/elements: fix search select inconsistency (#4989)
* web/elements: fix search-select inconsistency Signed-off-by: Jens Langhammer <jens@goauthentik.io> * web/common: fix config having to be json converted everywhere Signed-off-by: Jens Langhammer <jens@goauthentik.io> * web/elements: refactor form without iron-form Signed-off-by: Jens Langhammer <jens@goauthentik.io> * web/admin: fix misc Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		
							
								
								
									
										7656
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7656
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -74,8 +74,6 @@ | |||||||
|         "@lingui/detect-locale": "^3.17.2", |         "@lingui/detect-locale": "^3.17.2", | ||||||
|         "@lingui/macro": "^3.17.2", |         "@lingui/macro": "^3.17.2", | ||||||
|         "@patternfly/patternfly": "^4.224.2", |         "@patternfly/patternfly": "^4.224.2", | ||||||
|         "@polymer/iron-form": "^3.0.1", |  | ||||||
|         "@polymer/paper-input": "^3.2.1", |  | ||||||
|         "@rollup/plugin-babel": "^6.0.3", |         "@rollup/plugin-babel": "^6.0.3", | ||||||
|         "@rollup/plugin-commonjs": "^24.0.1", |         "@rollup/plugin-commonjs": "^24.0.1", | ||||||
|         "@rollup/plugin-node-resolve": "^15.0.1", |         "@rollup/plugin-node-resolve": "^15.0.1", | ||||||
|  | |||||||
| @ -61,6 +61,9 @@ export class LDAPSyncStatusChart extends AKChart<SyncStatus[]> { | |||||||
|                             metrics.healthy += 1; |                             metrics.healthy += 1; | ||||||
|                         } |                         } | ||||||
|                     }); |                     }); | ||||||
|  |                     if (health.length < 1) { | ||||||
|  |                         metrics.unsynced += 1; | ||||||
|  |                     } | ||||||
|                 } catch { |                 } catch { | ||||||
|                     metrics.unsynced += 1; |                     metrics.unsynced += 1; | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -10,9 +10,8 @@ import "@goauthentik/elements/forms/SearchSelect"; | |||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
|  |  | ||||||
| import { TemplateResult, html } from "lit"; | import { TemplateResult, html } from "lit"; | ||||||
| import { customElement } from "lit/decorators.js"; | import { customElement, state } from "lit/decorators.js"; | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; | import { ifDefined } from "lit/directives/if-defined.js"; | ||||||
| import { until } from "lit/directives/until.js"; |  | ||||||
|  |  | ||||||
| import { | import { | ||||||
|     CertificateKeyPair, |     CertificateKeyPair, | ||||||
| @ -20,6 +19,7 @@ import { | |||||||
|     CoreGroupsListRequest, |     CoreGroupsListRequest, | ||||||
|     CryptoApi, |     CryptoApi, | ||||||
|     CryptoCertificatekeypairsListRequest, |     CryptoCertificatekeypairsListRequest, | ||||||
|  |     CurrentTenant, | ||||||
|     Flow, |     Flow, | ||||||
|     FlowsApi, |     FlowsApi, | ||||||
|     FlowsInstancesListDesignationEnum, |     FlowsInstancesListDesignationEnum, | ||||||
| @ -32,10 +32,14 @@ import { | |||||||
|  |  | ||||||
| @customElement("ak-provider-ldap-form") | @customElement("ak-provider-ldap-form") | ||||||
| export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | ||||||
|     loadInstance(pk: number): Promise<LDAPProvider> { |     @state() | ||||||
|         return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ |     tenant?: CurrentTenant; | ||||||
|  |     async loadInstance(pk: number): Promise<LDAPProvider> { | ||||||
|  |         const provider = await new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ | ||||||
|             id: pk, |             id: pk, | ||||||
|         }); |         }); | ||||||
|  |         this.tenant = await tenant(); | ||||||
|  |         return provider; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getSuccessMessage(): string { |     getSuccessMessage(): string { | ||||||
| @ -75,46 +79,36 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | |||||||
|                 ?required=${true} |                 ?required=${true} | ||||||
|                 name="authorizationFlow" |                 name="authorizationFlow" | ||||||
|             > |             > | ||||||
|                 ${until( |                 <ak-search-select | ||||||
|                     tenant().then((t) => { |                     .fetchObjects=${async (query?: string): Promise<Flow[]> => { | ||||||
|                         return html` |                         const args: FlowsInstancesListRequest = { | ||||||
|                             <ak-search-select |                             ordering: "slug", | ||||||
|                                 .fetchObjects=${async (query?: string): Promise<Flow[]> => { |                             designation: FlowsInstancesListDesignationEnum.Authentication, | ||||||
|                                     const args: FlowsInstancesListRequest = { |                         }; | ||||||
|                                         ordering: "slug", |                         if (query !== undefined) { | ||||||
|                                         designation: |                             args.search = query; | ||||||
|                                             FlowsInstancesListDesignationEnum.Authentication, |                         } | ||||||
|                                     }; |                         const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args); | ||||||
|                                     if (query !== undefined) { |                         return flows.results; | ||||||
|                                         args.search = query; |                     }} | ||||||
|                                     } |                     .renderElement=${(flow: Flow): string => { | ||||||
|                                     const flows = await new FlowsApi( |                         return RenderFlowOption(flow); | ||||||
|                                         DEFAULT_CONFIG, |                     }} | ||||||
|                                     ).flowsInstancesList(args); |                     .renderDescription=${(flow: Flow): TemplateResult => { | ||||||
|                                     return flows.results; |                         return html`${flow.slug}`; | ||||||
|                                 }} |                     }} | ||||||
|                                 .renderElement=${(flow: Flow): string => { |                     .value=${(flow: Flow | undefined): string | undefined => { | ||||||
|                                     return RenderFlowOption(flow); |                         return flow?.pk; | ||||||
|                                 }} |                     }} | ||||||
|                                 .renderDescription=${(flow: Flow): TemplateResult => { |                     .selected=${(flow: Flow): boolean => { | ||||||
|                                     return html`${flow.slug}`; |                         let selected = flow.pk === this.tenant?.flowAuthentication; | ||||||
|                                 }} |                         if (this.instance?.authorizationFlow === flow.pk) { | ||||||
|                                 .value=${(flow: Flow | undefined): string | undefined => { |                             selected = true; | ||||||
|                                     return flow?.pk; |                         } | ||||||
|                                 }} |                         return selected; | ||||||
|                                 .selected=${(flow: Flow): boolean => { |                     }} | ||||||
|                                     let selected = flow.pk === t.flowAuthentication; |                 > | ||||||
|                                     if (this.instance?.authorizationFlow === flow.pk) { |                 </ak-search-select> | ||||||
|                                         selected = true; |  | ||||||
|                                     } |  | ||||||
|                                     return selected; |  | ||||||
|                                 }} |  | ||||||
|                             > |  | ||||||
|                             </ak-search-select> |  | ||||||
|                         `; |  | ||||||
|                     }), |  | ||||||
|                     html`<option>${t`Loading...`}</option>`, |  | ||||||
|                 )} |  | ||||||
|                 <p class="pf-c-form__helper-text"> |                 <p class="pf-c-form__helper-text"> | ||||||
|                     ${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`} |                     ${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`} | ||||||
|                 </p> |                 </p> | ||||||
|  | |||||||
| @ -7,19 +7,9 @@ import { EVENT_REFRESH, VERSION } from "@goauthentik/common/constants"; | |||||||
| import { globalAK } from "@goauthentik/common/global"; | import { globalAK } from "@goauthentik/common/global"; | ||||||
| import { activateLocale } from "@goauthentik/common/ui/locale"; | import { activateLocale } from "@goauthentik/common/ui/locale"; | ||||||
|  |  | ||||||
| import { | import { Config, Configuration, CoreApi, CurrentTenant, RootApi } from "@goauthentik/api"; | ||||||
|     Config, |  | ||||||
|     ConfigFromJSON, |  | ||||||
|     Configuration, |  | ||||||
|     CoreApi, |  | ||||||
|     CurrentTenant, |  | ||||||
|     CurrentTenantFromJSON, |  | ||||||
|     RootApi, |  | ||||||
| } from "@goauthentik/api"; |  | ||||||
|  |  | ||||||
| let globalConfigPromise: Promise<Config> | undefined = Promise.resolve( | let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config); | ||||||
|     ConfigFromJSON(globalAK()?.config), |  | ||||||
| ); |  | ||||||
| export function config(): Promise<Config> { | export function config(): Promise<Config> { | ||||||
|     if (!globalConfigPromise) { |     if (!globalConfigPromise) { | ||||||
|         globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve(); |         globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve(); | ||||||
| @ -52,9 +42,7 @@ export function tenantSetLocale(tenant: CurrentTenant) { | |||||||
|     activateLocale(tenant.defaultLocale); |     activateLocale(tenant.defaultLocale); | ||||||
| } | } | ||||||
|  |  | ||||||
| let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve( | let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve(globalAK().tenant); | ||||||
|     CurrentTenantFromJSON(globalAK()?.tenant), |  | ||||||
| ); |  | ||||||
| export function tenant(): Promise<CurrentTenant> { | export function tenant(): Promise<CurrentTenant> { | ||||||
|     if (!globalTenantPromise) { |     if (!globalTenantPromise) { | ||||||
|         globalTenantPromise = new CoreApi(DEFAULT_CONFIG) |         globalTenantPromise = new CoreApi(DEFAULT_CONFIG) | ||||||
| @ -82,7 +70,7 @@ export const DEFAULT_CONFIG = new Configuration({ | |||||||
|     middleware: [ |     middleware: [ | ||||||
|         new CSRFMiddleware(), |         new CSRFMiddleware(), | ||||||
|         new EventMiddleware(), |         new EventMiddleware(), | ||||||
|         new LoggingMiddleware(CurrentTenantFromJSON(globalAK()?.tenant)), |         new LoggingMiddleware(globalAK().tenant), | ||||||
|     ], |     ], | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { Config, CurrentTenant } from "@goauthentik/api"; | import { Config, ConfigFromJSON, CurrentTenant, CurrentTenantFromJSON } from "@goauthentik/api"; | ||||||
|  |  | ||||||
| export interface GlobalAuthentik { | export interface GlobalAuthentik { | ||||||
|  |     _converted?: boolean; | ||||||
|     locale?: string; |     locale?: string; | ||||||
|     flow?: { |     flow?: { | ||||||
|         layout: string; |         layout: string; | ||||||
| @ -13,11 +14,17 @@ export interface GlobalAuthentik { | |||||||
| } | } | ||||||
|  |  | ||||||
| export interface AuthentikWindow { | export interface AuthentikWindow { | ||||||
|     authentik?: GlobalAuthentik; |     authentik: GlobalAuthentik; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function globalAK(): GlobalAuthentik | undefined { | export function globalAK(): GlobalAuthentik { | ||||||
|     return (window as unknown as AuthentikWindow).authentik; |     const ak = (window as unknown as AuthentikWindow).authentik; | ||||||
|  |     if (ak && !ak._converted) { | ||||||
|  |         ak._converted = true; | ||||||
|  |         ak.tenant = CurrentTenantFromJSON(ak.tenant); | ||||||
|  |         ak.config = ConfigFromJSON(ak.config); | ||||||
|  |     } | ||||||
|  |     return ak; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function docLink(path: string): string { | export function docLink(path: string): string { | ||||||
|  | |||||||
| @ -172,6 +172,6 @@ export class Interface extends AKElement { | |||||||
|  |  | ||||||
|     async getTheme(): Promise<UiThemeEnum> { |     async getTheme(): Promise<UiThemeEnum> { | ||||||
|         const config = await uiConfig(); |         const config = await uiConfig(); | ||||||
|         return config.theme.base; |         return config.theme?.base || UiThemeEnum.Automatic; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,9 +5,6 @@ import { AKElement } from "@goauthentik/elements/Base"; | |||||||
| import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; | import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; | ||||||
| import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; | import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; | ||||||
| import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; | import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; | ||||||
| import "@polymer/iron-form/iron-form"; |  | ||||||
| import { IronFormElement } from "@polymer/iron-form/iron-form"; |  | ||||||
| import "@polymer/paper-input/paper-input"; |  | ||||||
|  |  | ||||||
| import { CSSResult, TemplateResult, css, html } from "lit"; | import { CSSResult, TemplateResult, css, html } from "lit"; | ||||||
| import { customElement, property } from "lit/decorators.js"; | import { customElement, property } from "lit/decorators.js"; | ||||||
| @ -110,63 +107,76 @@ export class Form<T> extends AKElement { | |||||||
|      * Reset the inner iron-form |      * Reset the inner iron-form | ||||||
|      */ |      */ | ||||||
|     resetForm(): void { |     resetForm(): void { | ||||||
|         const ironForm = this.shadowRoot?.querySelector("iron-form"); |         const form = this.shadowRoot?.querySelector<HTMLFormElement>("form"); | ||||||
|         ironForm?.reset(); |         form?.reset(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getFormFiles(): { [key: string]: File } { |     getFormFiles(): { [key: string]: File } { | ||||||
|         const ironForm = this.shadowRoot?.querySelector("iron-form"); |  | ||||||
|         const files: { [key: string]: File } = {}; |         const files: { [key: string]: File } = {}; | ||||||
|         if (!ironForm) { |         const elements = | ||||||
|             return files; |             this.shadowRoot?.querySelectorAll<HorizontalFormElement>( | ||||||
|         } |                 "ak-form-element-horizontal", | ||||||
|         const elements = ironForm._getSubmittableElements(); |             ) || []; | ||||||
|         for (let i = 0; i < elements.length; i++) { |         for (let i = 0; i < elements.length; i++) { | ||||||
|             const element = elements[i] as HTMLInputElement; |             const element = elements[i]; | ||||||
|             if (element.tagName.toLowerCase() === "input" && element.type === "file") { |             element.requestUpdate(); | ||||||
|                 if ((element.files || []).length < 1) { |             const inputElement = element.querySelector<HTMLInputElement>("[name]"); | ||||||
|  |             if (!inputElement) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if (inputElement.tagName.toLowerCase() === "input" && inputElement.type === "file") { | ||||||
|  |                 if ((inputElement.files || []).length < 1) { | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 files[element.name] = (element.files || [])[0]; |                 files[element.name] = (inputElement.files || [])[0]; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return files; |         return files; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     serializeForm(): T | undefined { |     serializeForm(): T | undefined { | ||||||
|         const form = this.shadowRoot?.querySelector<IronFormElement>("iron-form"); |         const elements = | ||||||
|         if (!form) { |             this.shadowRoot?.querySelectorAll<HorizontalFormElement>( | ||||||
|             console.warn("authentik/forms: failed to find iron-form"); |                 "ak-form-element-horizontal", | ||||||
|             return; |             ) || []; | ||||||
|         } |  | ||||||
|         const elements: HTMLInputElement[] = form._getSubmittableElements(); |  | ||||||
|         const json: { [key: string]: unknown } = {}; |         const json: { [key: string]: unknown } = {}; | ||||||
|         elements.forEach((element) => { |         elements.forEach((element) => { | ||||||
|             const values = form._serializeElementValues(element); |             element.requestUpdate(); | ||||||
|             if (element.hidden) { |             const inputElement = element.querySelector<HTMLInputElement>("[name]"); | ||||||
|  |             if (element.hidden || !inputElement) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             if (element.tagName.toLowerCase() === "select" && "multiple" in element.attributes) { |             if ( | ||||||
|                 json[element.name] = values; |                 inputElement.tagName.toLowerCase() === "select" && | ||||||
|             } else if (element.tagName.toLowerCase() === "input" && element.type === "date") { |                 "multiple" in inputElement.attributes | ||||||
|                 json[element.name] = element.valueAsDate; |  | ||||||
|             } else if ( |  | ||||||
|                 element.tagName.toLowerCase() === "input" && |  | ||||||
|                 element.type === "datetime-local" |  | ||||||
|             ) { |             ) { | ||||||
|                 json[element.name] = new Date(element.valueAsNumber); |                 const selectElement = inputElement as unknown as HTMLSelectElement; | ||||||
|  |                 json[element.name] = Array.from(selectElement.selectedOptions).map((v) => v.value); | ||||||
|             } else if ( |             } else if ( | ||||||
|                 element.tagName.toLowerCase() === "input" && |                 inputElement.tagName.toLowerCase() === "input" && | ||||||
|                 "type" in element.dataset && |                 inputElement.type === "date" | ||||||
|                 element.dataset["type"] === "datetime-local" |             ) { | ||||||
|  |                 json[element.name] = inputElement.valueAsDate; | ||||||
|  |             } else if ( | ||||||
|  |                 inputElement.tagName.toLowerCase() === "input" && | ||||||
|  |                 inputElement.type === "datetime-local" | ||||||
|  |             ) { | ||||||
|  |                 json[element.name] = new Date(inputElement.valueAsNumber); | ||||||
|  |             } else if ( | ||||||
|  |                 inputElement.tagName.toLowerCase() === "input" && | ||||||
|  |                 "type" in inputElement.dataset && | ||||||
|  |                 inputElement.dataset["type"] === "datetime-local" | ||||||
|             ) { |             ) { | ||||||
|                 // Workaround for Firefox <93, since 92 and older don't support |                 // Workaround for Firefox <93, since 92 and older don't support | ||||||
|                 // datetime-local fields |                 // datetime-local fields | ||||||
|                 json[element.name] = new Date(element.value); |                 json[element.name] = new Date(inputElement.value); | ||||||
|             } else if (element.tagName.toLowerCase() === "input" && element.type === "checkbox") { |             } else if ( | ||||||
|                 json[element.name] = element.checked; |                 inputElement.tagName.toLowerCase() === "input" && | ||||||
|             } else if (element.tagName.toLowerCase() === "ak-search-select") { |                 inputElement.type === "checkbox" | ||||||
|                 const select = element as unknown as SearchSelect<unknown>; |             ) { | ||||||
|  |                 json[element.name] = inputElement.checked; | ||||||
|  |             } else if (inputElement.tagName.toLowerCase() === "ak-search-select") { | ||||||
|  |                 const select = inputElement as unknown as SearchSelect<unknown>; | ||||||
|                 let value: unknown; |                 let value: unknown; | ||||||
|                 try { |                 try { | ||||||
|                     value = select.toForm(); |                     value = select.toForm(); | ||||||
| @ -179,9 +189,7 @@ export class Form<T> extends AKElement { | |||||||
|                 } |                 } | ||||||
|                 json[element.name] = value; |                 json[element.name] = value; | ||||||
|             } else { |             } else { | ||||||
|                 for (let v = 0; v < values.length; v++) { |                 this.serializeFieldRecursive(inputElement, inputElement.value, json); | ||||||
|                     this.serializeFieldRecursive(element, values[v], json); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         return json as unknown as T; |         return json as unknown as T; | ||||||
| @ -213,11 +221,6 @@ export class Form<T> extends AKElement { | |||||||
|         if (!data) { |         if (!data) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         const form = this.shadowRoot?.querySelector<IronFormElement>("iron-form"); |  | ||||||
|         if (!form) { |  | ||||||
|             console.warn("authentik/forms: failed to find iron-form"); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         return this.send(data) |         return this.send(data) | ||||||
|             .then((r) => { |             .then((r) => { | ||||||
|                 showMessage({ |                 showMessage({ | ||||||
| @ -244,8 +247,12 @@ export class Form<T> extends AKElement { | |||||||
|                         throw errorMessage; |                         throw errorMessage; | ||||||
|                     } |                     } | ||||||
|                     // assign all input-related errors to their elements |                     // assign all input-related errors to their elements | ||||||
|                     const elements: HorizontalFormElement[] = form._getSubmittableElements(); |                     const elements = | ||||||
|  |                         this.shadowRoot?.querySelectorAll<HorizontalFormElement>( | ||||||
|  |                             "ak-form-element-horizontal", | ||||||
|  |                         ) || []; | ||||||
|                     elements.forEach((element) => { |                     elements.forEach((element) => { | ||||||
|  |                         element.requestUpdate(); | ||||||
|                         const elementName = element.name; |                         const elementName = element.name; | ||||||
|                         if (!elementName) return; |                         if (!elementName) return; | ||||||
|                         if (camelToSnake(elementName) in errorMessage) { |                         if (camelToSnake(elementName) in errorMessage) { | ||||||
| @ -296,13 +303,7 @@ export class Form<T> extends AKElement { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderVisible(): TemplateResult { |     renderVisible(): TemplateResult { | ||||||
|         return html`<iron-form |         return html` ${this.renderNonFieldErrors()} ${this.renderForm()}`; | ||||||
|             @iron-form-presubmit=${(ev: Event) => { |  | ||||||
|                 this.submit(ev); |  | ||||||
|             }} |  | ||||||
|         > |  | ||||||
|             ${this.renderNonFieldErrors()} ${this.renderForm()} |  | ||||||
|         </iron-form>`; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     render(): TemplateResult { |     render(): TemplateResult { | ||||||
|  | |||||||
| @ -69,6 +69,10 @@ export class HorizontalFormElement extends AKElement { | |||||||
|     @property() |     @property() | ||||||
|     name = ""; |     name = ""; | ||||||
|  |  | ||||||
|  |     firstUpdated(): void { | ||||||
|  |         this.updated(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     updated(): void { |     updated(): void { | ||||||
|         this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => { |         this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => { | ||||||
|             input.focus(); |             input.focus(); | ||||||
| @ -89,7 +93,7 @@ export class HorizontalFormElement extends AKElement { | |||||||
|                 case "ak-chip-group": |                 case "ak-chip-group": | ||||||
|                 case "ak-search-select": |                 case "ak-search-select": | ||||||
|                 case "ak-radio": |                 case "ak-radio": | ||||||
|                     (input as HTMLInputElement).name = this.name; |                     input.setAttribute("name", this.name); | ||||||
|                     break; |                     break; | ||||||
|                 default: |                 default: | ||||||
|                     return; |                     return; | ||||||
| @ -108,6 +112,7 @@ export class HorizontalFormElement extends AKElement { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     render(): TemplateResult { |     render(): TemplateResult { | ||||||
|  |         this.updated(); | ||||||
|         return html`<div class="pf-c-form__group"> |         return html`<div class="pf-c-form__group"> | ||||||
|             <div class="pf-c-form__group-label"> |             <div class="pf-c-form__group-label"> | ||||||
|                 <label class="pf-c-form__label"> |                 <label class="pf-c-form__label"> | ||||||
|  | |||||||
| @ -70,6 +70,7 @@ export class SearchSelect<T> extends AKElement { | |||||||
|     observer: IntersectionObserver; |     observer: IntersectionObserver; | ||||||
|     dropdownUID: string; |     dropdownUID: string; | ||||||
|     dropdownContainer: HTMLDivElement; |     dropdownContainer: HTMLDivElement; | ||||||
|  |     isFetchingData = false; | ||||||
|  |  | ||||||
|     constructor() { |     constructor() { | ||||||
|         super(); |         super(); | ||||||
| @ -103,13 +104,18 @@ export class SearchSelect<T> extends AKElement { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     updateData(): void { |     updateData(): void { | ||||||
|  |         if (this.isFetchingData) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         this.isFetchingData = true; | ||||||
|         this.fetchObjects(this.query).then((objects) => { |         this.fetchObjects(this.query).then((objects) => { | ||||||
|             this.objects = objects; |             objects.forEach((obj) => { | ||||||
|             this.objects.forEach((obj) => { |  | ||||||
|                 if (this.selected && this.selected(obj, this.objects || [])) { |                 if (this.selected && this.selected(obj, this.objects || [])) { | ||||||
|                     this.selectedObject = obj; |                     this.selectedObject = obj; | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|  |             this.objects = objects; | ||||||
|  |             this.isFetchingData = false; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -200,9 +206,10 @@ export class SearchSelect<T> extends AKElement { | |||||||
|         render( |         render( | ||||||
|             html`<div |             html`<div | ||||||
|                 class="pf-c-dropdown pf-m-expanded" |                 class="pf-c-dropdown pf-m-expanded" | ||||||
|                 ?hidden=${!this.open} |  | ||||||
|                 style="position: fixed; inset: 0px auto auto 0px; z-index: 9999; transform: translate(${pos.x}px, ${pos.y + |                 style="position: fixed; inset: 0px auto auto 0px; z-index: 9999; transform: translate(${pos.x}px, ${pos.y + | ||||||
|                 this.offsetHeight}px); width: ${pos.width}px;" |                 this.offsetHeight}px); width: ${pos.width}px; ${this.open | ||||||
|  |                     ? "" | ||||||
|  |                     : "visibility: hidden;"}" | ||||||
|             > |             > | ||||||
|                 <ul |                 <ul | ||||||
|                     class="pf-c-dropdown__menu pf-m-static" |                     class="pf-c-dropdown__menu pf-m-static" | ||||||
| @ -249,6 +256,14 @@ export class SearchSelect<T> extends AKElement { | |||||||
|  |  | ||||||
|     render(): TemplateResult { |     render(): TemplateResult { | ||||||
|         this.renderMenu(); |         this.renderMenu(); | ||||||
|  |         let value = ""; | ||||||
|  |         if (!this.objects) { | ||||||
|  |             value = t`Loading...`; | ||||||
|  |         } else if (this.selectedObject) { | ||||||
|  |             value = this.renderElement(this.selectedObject); | ||||||
|  |         } else if (this.blankable) { | ||||||
|  |             value = this.emptyOption; | ||||||
|  |         } | ||||||
|         return html`<div class="pf-c-select"> |         return html`<div class="pf-c-select"> | ||||||
|             <div class="pf-c-select__toggle pf-m-typeahead"> |             <div class="pf-c-select__toggle pf-m-typeahead"> | ||||||
|                 <div class="pf-c-select__toggle-wrapper"> |                 <div class="pf-c-select__toggle-wrapper"> | ||||||
| @ -256,6 +271,7 @@ export class SearchSelect<T> extends AKElement { | |||||||
|                         class="pf-c-form-control pf-c-select__toggle-typeahead" |                         class="pf-c-form-control pf-c-select__toggle-typeahead" | ||||||
|                         type="text" |                         type="text" | ||||||
|                         placeholder=${this.placeholder} |                         placeholder=${this.placeholder} | ||||||
|  |                         spellcheck="false" | ||||||
|                         @input=${(ev: InputEvent) => { |                         @input=${(ev: InputEvent) => { | ||||||
|                             this.query = (ev.target as HTMLInputElement).value; |                             this.query = (ev.target as HTMLInputElement).value; | ||||||
|                             this.updateData(); |                             this.updateData(); | ||||||
| @ -285,11 +301,7 @@ export class SearchSelect<T> extends AKElement { | |||||||
|                             this.open = false; |                             this.open = false; | ||||||
|                             this.renderMenu(); |                             this.renderMenu(); | ||||||
|                         }} |                         }} | ||||||
|                         .value=${this.selectedObject |                         .value=${value} | ||||||
|                             ? this.renderElement(this.selectedObject) |  | ||||||
|                             : this.blankable |  | ||||||
|                             ? this.emptyOption |  | ||||||
|                             : ""} |  | ||||||
|                     /> |                     /> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -37,6 +37,7 @@ import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; | |||||||
| import PFList from "@patternfly/patternfly/components/List/list.css"; | import PFList from "@patternfly/patternfly/components/List/list.css"; | ||||||
| import PFLogin from "@patternfly/patternfly/components/Login/login.css"; | import PFLogin from "@patternfly/patternfly/components/Login/login.css"; | ||||||
| import PFTitle from "@patternfly/patternfly/components/Title/title.css"; | import PFTitle from "@patternfly/patternfly/components/Title/title.css"; | ||||||
|  | import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||||
|  |  | ||||||
| import { | import { | ||||||
|     ChallengeChoices, |     ChallengeChoices, | ||||||
| @ -119,7 +120,7 @@ export class FlowExecutor extends Interface implements StageHost { | |||||||
|     ws: WebsocketClient; |     ws: WebsocketClient; | ||||||
|  |  | ||||||
|     static get styles(): CSSResult[] { |     static get styles(): CSSResult[] { | ||||||
|         return [PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css` |         return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css` | ||||||
|             .pf-c-background-image::before { |             .pf-c-background-image::before { | ||||||
|                 --pf-c-background-image--BackgroundImage: var(--ak-flow-background); |                 --pf-c-background-image--BackgroundImage: var(--ak-flow-background); | ||||||
|                 --pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background); |                 --pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background); | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ import PFForm from "@patternfly/patternfly/components/Form/form.css"; | |||||||
| import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; | import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; | ||||||
| import PFLogin from "@patternfly/patternfly/components/Login/login.css"; | import PFLogin from "@patternfly/patternfly/components/Login/login.css"; | ||||||
| import PFTitle from "@patternfly/patternfly/components/Title/title.css"; | import PFTitle from "@patternfly/patternfly/components/Title/title.css"; | ||||||
|  | import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||||
|  |  | ||||||
| import { | import { | ||||||
|     AuthenticatorValidationChallenge, |     AuthenticatorValidationChallenge, | ||||||
| @ -76,7 +77,7 @@ export class AuthenticatorValidateStage | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     static get styles(): CSSResult[] { |     static get styles(): CSSResult[] { | ||||||
|         return [PFLogin, PFForm, PFFormControl, PFTitle, PFButton].concat(css` |         return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton].concat(css` | ||||||
|             ul { |             ul { | ||||||
|                 padding-top: 1rem; |                 padding-top: 1rem; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -68,6 +68,7 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng | |||||||
|         if (this.timer) { |         if (this.timer) { | ||||||
|             console.debug("authentik/stages/password: cleared focus timer"); |             console.debug("authentik/stages/password: cleared focus timer"); | ||||||
|             window.clearInterval(this.timer); |             window.clearInterval(this.timer); | ||||||
|  |             this.timer = undefined; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| // @ts-ignore |  | ||||||
| window["polymerSkipLoadingFontRoboto"] = true; |  | ||||||
| import "construct-style-sheets-polyfill"; | import "construct-style-sheets-polyfill"; | ||||||
| import "@webcomponents/webcomponentsjs"; | import "@webcomponents/webcomponentsjs"; | ||||||
| import "lit/polyfill-support.js"; | import "lit/polyfill-support.js"; | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L