web: lazy load parts of interfaces (#2864)
This commit is contained in:
		| @ -70,24 +70,32 @@ export function manualChunks(id) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| export const PLUGINS = [ | ||||
|     cssimport(), | ||||
|     markdown(), | ||||
|     nodeResolve({ extensions, browser: true }), | ||||
|     commonjs(), | ||||
|     babel({ | ||||
|         extensions, | ||||
|         babelHelpers: "runtime", | ||||
|         include: ["src/**/*"], | ||||
|     }), | ||||
|     replace({ | ||||
|         "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"), | ||||
|         "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath), | ||||
|         "preventAssignment": true, | ||||
|     }), | ||||
|     sourcemaps(), | ||||
|     isProdBuild && terser(), | ||||
| ].filter((p) => p); | ||||
| export const defaultOptions = { | ||||
|     plugins: [ | ||||
|         cssimport(), | ||||
|         markdown(), | ||||
|         nodeResolve({ extensions, browser: true }), | ||||
|         commonjs(), | ||||
|         babel({ | ||||
|             extensions, | ||||
|             babelHelpers: "runtime", | ||||
|             include: ["src/**/*"], | ||||
|         }), | ||||
|         replace({ | ||||
|             "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"), | ||||
|             "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath), | ||||
|             "preventAssignment": true, | ||||
|         }), | ||||
|         sourcemaps(), | ||||
|         isProdBuild && terser(), | ||||
|     ].filter((p) => p), | ||||
|     watch: { | ||||
|         clearScreen: false, | ||||
|     }, | ||||
|     preserveEntrySignatures: false, | ||||
|     cache: true, | ||||
|     context: "window", | ||||
| }; | ||||
|  | ||||
| // Polyfills (imported first) | ||||
| export const POLY = { | ||||
| @ -110,9 +118,6 @@ export const POLY = { | ||||
|             copyOnce: false, | ||||
|         }), | ||||
|     ].filter((p) => p), | ||||
|     watch: { | ||||
|         clearScreen: false, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| export default [ | ||||
| @ -120,8 +125,6 @@ export default [ | ||||
|     // Flow interface | ||||
|     { | ||||
|         input: "./src/interfaces/FlowInterface.ts", | ||||
|         context: "window", | ||||
|         cache: true, | ||||
|         output: [ | ||||
|             { | ||||
|                 format: "es", | ||||
| @ -130,16 +133,11 @@ export default [ | ||||
|                 manualChunks: manualChunks, | ||||
|             }, | ||||
|         ], | ||||
|         plugins: PLUGINS, | ||||
|         watch: { | ||||
|             clearScreen: false, | ||||
|         }, | ||||
|         ...defaultOptions, | ||||
|     }, | ||||
|     // Admin interface | ||||
|     { | ||||
|         input: "./src/interfaces/AdminInterface.ts", | ||||
|         context: "window", | ||||
|         cache: true, | ||||
|         output: [ | ||||
|             { | ||||
|                 format: "es", | ||||
| @ -148,16 +146,11 @@ export default [ | ||||
|                 manualChunks: manualChunks, | ||||
|             }, | ||||
|         ], | ||||
|         plugins: PLUGINS, | ||||
|         watch: { | ||||
|             clearScreen: false, | ||||
|         }, | ||||
|         ...defaultOptions, | ||||
|     }, | ||||
|     // User interface | ||||
|     { | ||||
|         input: "./src/interfaces/UserInterface.ts", | ||||
|         context: "window", | ||||
|         cache: true, | ||||
|         output: [ | ||||
|             { | ||||
|                 format: "es", | ||||
| @ -166,9 +159,6 @@ export default [ | ||||
|                 manualChunks: manualChunks, | ||||
|             }, | ||||
|         ], | ||||
|         plugins: PLUGINS, | ||||
|         watch: { | ||||
|             clearScreen: false, | ||||
|         }, | ||||
|         ...defaultOptions, | ||||
|     }, | ||||
| ]; | ||||
|  | ||||
| @ -95,6 +95,11 @@ html > form > input { | ||||
|     vertical-align: middle; | ||||
| } | ||||
|  | ||||
| .pf-c-description-list__description .pf-c-button { | ||||
|     margin-right: 6px; | ||||
|     margin-bottom: 6px; | ||||
| } | ||||
|  | ||||
| @media (prefers-color-scheme: dark) { | ||||
|     .ak-static-page h1 { | ||||
|         color: var(--ak-dark-foreground); | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import { TemplateResult, html } from "lit"; | ||||
| import { until } from "lit/directives/until.js"; | ||||
|  | ||||
| export const SLUG_REGEX = "[-a-zA-Z0-9_]+"; | ||||
| export const ID_REGEX = "\\d+"; | ||||
| @ -12,39 +13,41 @@ export class Route { | ||||
|     url: RegExp; | ||||
|  | ||||
|     private element?: TemplateResult; | ||||
|     private callback?: (args: RouteArgs) => TemplateResult; | ||||
|     private callback?: (args: RouteArgs) => Promise<TemplateResult>; | ||||
|  | ||||
|     constructor(url: RegExp, element?: TemplateResult) { | ||||
|     constructor(url: RegExp, callback?: (args: RouteArgs) => Promise<TemplateResult>) { | ||||
|         this.url = url; | ||||
|         this.element = element; | ||||
|         this.callback = callback; | ||||
|     } | ||||
|  | ||||
|     redirect(to: string): Route { | ||||
|         this.callback = () => { | ||||
|     redirect(to: string, raw = false): Route { | ||||
|         this.callback = async () => { | ||||
|             console.debug(`authentik/router: redirecting ${to}`); | ||||
|             window.location.hash = `#${to}`; | ||||
|             return html``; | ||||
|         }; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     redirectRaw(to: string): Route { | ||||
|         this.callback = () => { | ||||
|             console.debug(`authentik/router: redirecting ${to}`); | ||||
|             window.location.hash = `${to}`; | ||||
|             if (!raw) { | ||||
|                 window.location.hash = `#${to}`; | ||||
|             } else { | ||||
|                 window.location.hash = to; | ||||
|             } | ||||
|             return html``; | ||||
|         }; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     then(render: (args: RouteArgs) => TemplateResult): Route { | ||||
|         this.callback = async (args) => { | ||||
|             return render(args); | ||||
|         }; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     thenAsync(render: (args: RouteArgs) => Promise<TemplateResult>): Route { | ||||
|         this.callback = render; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     render(args: RouteArgs): TemplateResult { | ||||
|         if (this.callback) { | ||||
|             return this.callback(args); | ||||
|             return html`${until(this.callback(args))}`; | ||||
|         } | ||||
|         if (this.element) { | ||||
|             return this.element; | ||||
|  | ||||
| @ -30,26 +30,13 @@ import { WebsocketClient } from "../common/ws"; | ||||
| import { EVENT_FLOW_ADVANCE, TITLE_DEFAULT } from "../constants"; | ||||
| import "../elements/LoadingOverlay"; | ||||
| import { first } from "../utils"; | ||||
| import "./FlowInspector"; | ||||
| import "./sources/apple/AppleLoginInit"; | ||||
| import "./sources/plex/PlexLoginInit"; | ||||
| import "./stages/RedirectStage"; | ||||
| import "./stages/access_denied/AccessDeniedStage"; | ||||
| import "./stages/authenticator_duo/AuthenticatorDuoStage"; | ||||
| import "./stages/authenticator_sms/AuthenticatorSMSStage"; | ||||
| import "./stages/authenticator_static/AuthenticatorStaticStage"; | ||||
| import "./stages/authenticator_totp/AuthenticatorTOTPStage"; | ||||
| import "./stages/authenticator_validate/AuthenticatorValidateStage"; | ||||
| import "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage"; | ||||
| import "./stages/autosubmit/AutosubmitStage"; | ||||
| import { StageHost } from "./stages/base"; | ||||
| import "./stages/captcha/CaptchaStage"; | ||||
| import "./stages/consent/ConsentStage"; | ||||
| import "./stages/dummy/DummyStage"; | ||||
| import "./stages/email/EmailStage"; | ||||
| import "./stages/identification/IdentificationStage"; | ||||
| import "./stages/password/PasswordStage"; | ||||
| import "./stages/prompt/PromptStage"; | ||||
|  | ||||
| @customElement("ak-flow-executor") | ||||
| export class FlowExecutor extends LitElement implements StageHost { | ||||
| @ -229,7 +216,117 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|         } as ChallengeTypes; | ||||
|     } | ||||
|  | ||||
|     renderChallenge(): TemplateResult { | ||||
|     async renderChallengeNativeElement(): Promise<TemplateResult> { | ||||
|         switch (this.challenge?.component) { | ||||
|             case "ak-stage-access-denied": | ||||
|                 // Statically imported for performance reasons | ||||
|                 return html`<ak-stage-access-denied | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-access-denied>`; | ||||
|             case "ak-stage-identification": | ||||
|                 // Statically imported for performance reasons | ||||
|                 return html`<ak-stage-identification | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-identification>`; | ||||
|             case "ak-stage-password": | ||||
|                 // Statically imported for performance reasons | ||||
|                 return html`<ak-stage-password | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-password>`; | ||||
|             case "ak-stage-captcha": | ||||
|                 // Statically imported to prevent browsers blocking urls | ||||
|                 return html`<ak-stage-captcha | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-captcha>`; | ||||
|             case "ak-stage-consent": | ||||
|                 await import("./stages/consent/ConsentStage"); | ||||
|                 return html`<ak-stage-consent | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-consent>`; | ||||
|             case "ak-stage-dummy": | ||||
|                 await import("./stages/dummy/DummyStage"); | ||||
|                 return html`<ak-stage-dummy | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-dummy>`; | ||||
|             case "ak-stage-email": | ||||
|                 await import("./stages/email/EmailStage"); | ||||
|                 return html`<ak-stage-email | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-email>`; | ||||
|             case "ak-stage-autosubmit": | ||||
|                 // Statically imported for performance reasons | ||||
|                 return html`<ak-stage-autosubmit | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-autosubmit>`; | ||||
|             case "ak-stage-prompt": | ||||
|                 await import("./stages/prompt/PromptStage"); | ||||
|                 return html`<ak-stage-prompt | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-prompt>`; | ||||
|             case "ak-stage-authenticator-totp": | ||||
|                 await import("./stages/authenticator_totp/AuthenticatorTOTPStage"); | ||||
|                 return html`<ak-stage-authenticator-totp | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-totp>`; | ||||
|             case "ak-stage-authenticator-duo": | ||||
|                 await import("./stages/authenticator_duo/AuthenticatorDuoStage"); | ||||
|                 return html`<ak-stage-authenticator-duo | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-duo>`; | ||||
|             case "ak-stage-authenticator-static": | ||||
|                 await import("./stages/authenticator_static/AuthenticatorStaticStage"); | ||||
|                 return html`<ak-stage-authenticator-static | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-static>`; | ||||
|             case "ak-stage-authenticator-webauthn": | ||||
|                 await import("./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage"); | ||||
|                 return html`<ak-stage-authenticator-webauthn | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-webauthn>`; | ||||
|             case "ak-stage-authenticator-sms": | ||||
|                 await import("./stages/authenticator_sms/AuthenticatorSMSStage"); | ||||
|                 return html`<ak-stage-authenticator-sms | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-sms>`; | ||||
|             case "ak-stage-authenticator-validate": | ||||
|                 await import("./stages/authenticator_validate/AuthenticatorValidateStage"); | ||||
|                 return html`<ak-stage-authenticator-validate | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-stage-authenticator-validate>`; | ||||
|             case "ak-flow-sources-plex": | ||||
|                 await import("./sources/plex/PlexLoginInit"); | ||||
|                 return html`<ak-flow-sources-plex | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-flow-sources-plex>`; | ||||
|             case "ak-flow-sources-oauth-apple": | ||||
|                 await import("./sources/apple/AppleLoginInit"); | ||||
|                 return html`<ak-flow-sources-oauth-apple | ||||
|                     .host=${this as StageHost} | ||||
|                     .challenge=${this.challenge} | ||||
|                 ></ak-flow-sources-oauth-apple>`; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|         return html`Invalid native challenge element`; | ||||
|     } | ||||
|  | ||||
|     async renderChallenge(): Promise<TemplateResult> { | ||||
|         if (!this.challenge) { | ||||
|             return html``; | ||||
|         } | ||||
| @ -247,96 +344,7 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|             case ChallengeChoices.Shell: | ||||
|                 return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`; | ||||
|             case ChallengeChoices.Native: | ||||
|                 switch (this.challenge.component) { | ||||
|                     case "ak-stage-access-denied": | ||||
|                         return html`<ak-stage-access-denied | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-access-denied>`; | ||||
|                     case "ak-stage-identification": | ||||
|                         return html`<ak-stage-identification | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-identification>`; | ||||
|                     case "ak-stage-password": | ||||
|                         return html`<ak-stage-password | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-password>`; | ||||
|                     case "ak-stage-captcha": | ||||
|                         return html`<ak-stage-captcha | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-captcha>`; | ||||
|                     case "ak-stage-consent": | ||||
|                         return html`<ak-stage-consent | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-consent>`; | ||||
|                     case "ak-stage-dummy": | ||||
|                         return html`<ak-stage-dummy | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-dummy>`; | ||||
|                     case "ak-stage-email": | ||||
|                         return html`<ak-stage-email | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-email>`; | ||||
|                     case "ak-stage-autosubmit": | ||||
|                         return html`<ak-stage-autosubmit | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-autosubmit>`; | ||||
|                     case "ak-stage-prompt": | ||||
|                         return html`<ak-stage-prompt | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-prompt>`; | ||||
|                     case "ak-stage-authenticator-totp": | ||||
|                         return html`<ak-stage-authenticator-totp | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-authenticator-totp>`; | ||||
|                     case "ak-stage-authenticator-duo": | ||||
|                         return html`<ak-stage-authenticator-duo | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-authenticator-duo>`; | ||||
|                     case "ak-stage-authenticator-static": | ||||
|                         return html`<ak-stage-authenticator-static | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-authenticator-static>`; | ||||
|                     case "ak-stage-authenticator-webauthn": | ||||
|                         return html`<ak-stage-authenticator-webauthn | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-authenticator-webauthn>`; | ||||
|                     case "ak-stage-authenticator-validate": | ||||
|                         return html`<ak-stage-authenticator-validate | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-authenticator-validate>`; | ||||
|                     case "ak-stage-authenticator-sms": | ||||
|                         return html`<ak-stage-authenticator-sms | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-stage-authenticator-sms>`; | ||||
|                     case "ak-flow-sources-plex": | ||||
|                         return html`<ak-flow-sources-plex | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-flow-sources-plex>`; | ||||
|                     case "ak-flow-sources-oauth-apple": | ||||
|                         return html`<ak-flow-sources-oauth-apple | ||||
|                             .host=${this as StageHost} | ||||
|                             .challenge=${this.challenge} | ||||
|                         ></ak-flow-sources-oauth-apple>`; | ||||
|                     default: | ||||
|                         break; | ||||
|                 } | ||||
|                 break; | ||||
|                 return await this.renderChallengeNativeElement(); | ||||
|             default: | ||||
|                 console.debug(`authentik/flows: unexpected data type ${this.challenge.type}`); | ||||
|                 break; | ||||
| @ -350,10 +358,20 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|         } | ||||
|         return html` | ||||
|             ${this.loading ? html`<ak-loading-overlay></ak-loading-overlay>` : html``} | ||||
|             ${this.renderChallenge()} | ||||
|             ${until(this.renderChallenge())} | ||||
|         `; | ||||
|     } | ||||
|  | ||||
|     async renderInspector(): Promise<TemplateResult> { | ||||
|         if (!this.inspectorOpen) { | ||||
|             return html``; | ||||
|         } | ||||
|         await import("./FlowInspector"); | ||||
|         return html`<ak-flow-inspector | ||||
|             class="pf-c-drawer__panel pf-m-width-33" | ||||
|         ></ak-flow-inspector>`; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<div class="pf-c-background-image"> | ||||
|                 <svg | ||||
| @ -439,13 +457,7 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <ak-flow-inspector | ||||
|                             class="pf-c-drawer__panel pf-m-width-33 ${this.inspectorOpen | ||||
|                                 ? "" | ||||
|                                 : "display-none"}" | ||||
|                             ?hidden=${!this.inspectorOpen} | ||||
|                         ></ak-flow-inspector> | ||||
|                         ${until(this.renderInspector())} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div>`; | ||||
|  | ||||
| @ -1,96 +1,122 @@ | ||||
| import { de, en, es, fr, pl, tr, zh } from "make-plural/plurals"; | ||||
|  | ||||
| import { Messages, i18n } from "@lingui/core"; | ||||
| import { detect, fromNavigator, fromUrl } from "@lingui/detect-locale"; | ||||
| import { t } from "@lingui/macro"; | ||||
|  | ||||
| import { LitElement } from "lit"; | ||||
|  | ||||
| import { messages as localeDE } from "../locales/de"; | ||||
| import { messages as localeEN } from "../locales/en"; | ||||
| import { messages as localeES } from "../locales/es"; | ||||
| import { messages as localeFR_FR } from "../locales/fr_FR"; | ||||
| import { messages as localePL } from "../locales/pl"; | ||||
| import { messages as localeDEBUG } from "../locales/pseudo-LOCALE"; | ||||
| import { messages as localeTR } from "../locales/tr"; | ||||
| import { messages as localeZH_Hans } from "../locales/zh-Hans"; | ||||
| import { messages as localeZH_Hant } from "../locales/zh-Hant"; | ||||
| import { messages as localeZH_TW } from "../locales/zh_TW"; | ||||
| interface Locale { | ||||
|     locale: Messages; | ||||
|     // eslint-disable-next-line @typescript-eslint/ban-types | ||||
|     plurals: Function; | ||||
| } | ||||
|  | ||||
| export const LOCALES: { | ||||
|     code: string; | ||||
|     label: string; | ||||
|     // eslint-disable-next-line @typescript-eslint/ban-types | ||||
|     plurals: Function; | ||||
|     locale: Messages; | ||||
|     locale: () => Promise<Locale>; | ||||
| }[] = [ | ||||
|     { | ||||
|         code: "en", | ||||
|         plurals: en, | ||||
|         label: t`English`, | ||||
|         locale: localeEN, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/en")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).en, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "debug", | ||||
|         plurals: en, | ||||
|         label: t`Debug`, | ||||
|         locale: localeDEBUG, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/pseudo-LOCALE")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).en, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "fr", | ||||
|         plurals: fr, | ||||
|         label: t`French`, | ||||
|         locale: localeFR_FR, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/fr_FR")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).fr, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "tr", | ||||
|         plurals: tr, | ||||
|         label: t`Turkish`, | ||||
|         locale: localeTR, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/tr")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).tr, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "es", | ||||
|         plurals: es, | ||||
|         label: t`Spanish`, | ||||
|         locale: localeES, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/es")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).es, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "pl", | ||||
|         plurals: pl, | ||||
|         label: t`Polish`, | ||||
|         locale: localePL, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/pl")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).pl, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "zh_TW", | ||||
|         plurals: zh, | ||||
|         label: t`Taiwanese Mandarin`, | ||||
|         locale: localeZH_TW, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/zh_TW")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).zh, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "zh-CN", | ||||
|         plurals: zh, | ||||
|         label: t`Chinese (simplified)`, | ||||
|         locale: localeZH_Hans, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/zh-Hans")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).zh, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "zh-HK", | ||||
|         plurals: zh, | ||||
|         label: t`Chinese (traditional)`, | ||||
|         locale: localeZH_Hant, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/zh-Hant")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).zh, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         code: "de", | ||||
|         plurals: de, | ||||
|         label: t`German`, | ||||
|         locale: localeDE, | ||||
|         locale: async () => { | ||||
|             return { | ||||
|                 locale: (await import("../locales/de")).messages, | ||||
|                 plurals: (await import("make-plural/plurals")).de, | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
| ]; | ||||
|  | ||||
| LOCALES.forEach((locale) => { | ||||
|     i18n.loadLocaleData(locale.code, { plurals: locale.plurals }); | ||||
|     i18n.load(locale.code, locale.locale); | ||||
| }); | ||||
|  | ||||
| const DEFAULT_FALLBACK = () => "en"; | ||||
|  | ||||
| export function autoDetectLanguage() { | ||||
| @ -102,25 +128,33 @@ export function autoDetectLanguage() { | ||||
|     } | ||||
|     if (detected in i18n._messages) { | ||||
|         console.debug(`authentik/locale: Activating detected locale '${detected}'`); | ||||
|         i18n.activate(detected); | ||||
|         activateLocale(detected); | ||||
|     } else { | ||||
|         console.debug(`authentik/locale: No locale for '${detected}', falling back to en`); | ||||
|         i18n.activate(DEFAULT_FALLBACK()); | ||||
|         activateLocale(DEFAULT_FALLBACK()); | ||||
|     } | ||||
| } | ||||
| export function activateLocale(code: string) { | ||||
|     const urlLocale = fromUrl("locale"); | ||||
|     if (urlLocale !== null && urlLocale !== "") { | ||||
|         i18n.activate(urlLocale); | ||||
|     } else { | ||||
|         i18n.activate(code); | ||||
|         code = urlLocale; | ||||
|     } | ||||
|     document.querySelectorAll("[data-refresh-on-locale=true]").forEach((el) => { | ||||
|         try { | ||||
|             (el as LitElement).requestUpdate(); | ||||
|         } catch { | ||||
|             console.debug(`authentik/locale: failed to update element ${el}`); | ||||
|         } | ||||
|     const locale = LOCALES.find((locale) => locale.code == code); | ||||
|     if (!locale) { | ||||
|         console.warn(`authentik/locale: failed to find locale for code ${code}`); | ||||
|         return; | ||||
|     } | ||||
|     locale.locale().then((localeData) => { | ||||
|         i18n.loadLocaleData(locale.code, { plurals: localeData.plurals }); | ||||
|         i18n.load(locale.code, localeData.locale); | ||||
|         i18n.activate(locale.code); | ||||
|         document.querySelectorAll("[data-refresh-on-locale=true]").forEach((el) => { | ||||
|             try { | ||||
|                 (el as LitElement).requestUpdate(); | ||||
|             } catch { | ||||
|                 console.debug(`authentik/locale: failed to update element ${el}`); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| autoDetectLanguage(); | ||||
|  | ||||
| @ -2824,6 +2824,7 @@ msgstr "Server laden" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2876,6 +2876,7 @@ msgstr "Load servers" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2817,6 +2817,7 @@ msgstr "Servidores de carga" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2848,6 +2848,7 @@ msgstr "Charger les serveurs" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2814,6 +2814,7 @@ msgstr "Załaduj serwery" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2858,6 +2858,7 @@ msgstr "" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2818,6 +2818,7 @@ msgstr "Sunucuları yükle" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2802,6 +2802,7 @@ msgstr "加载服务器" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2804,6 +2804,7 @@ msgstr "加载服务器" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -2804,6 +2804,7 @@ msgstr "加载服务器" | ||||
| #: src/elements/table/Table.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowExecutor.ts | ||||
| #: src/flows/FlowInspector.ts | ||||
| #: src/flows/stages/access_denied/AccessDeniedStage.ts | ||||
| #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts | ||||
|  | ||||
| @ -163,6 +163,11 @@ export class FlowViewPage extends LitElement { | ||||
|                                                                     `inspector&next=/#${window.location.hash}`, | ||||
|                                                                 )}`; | ||||
|                                                                 window.open(finalURL, "_blank"); | ||||
|                                                             }) | ||||
|                                                             .catch((exc: Response) => { | ||||
|                                                                 // This request can return a HTTP 400 when a flow | ||||
|                                                                 // is not applicable. | ||||
|                                                                 window.open(exc.url, "_blank"); | ||||
|                                                             }); | ||||
|                                                     }} | ||||
|                                                 > | ||||
|  | ||||
| @ -2,116 +2,130 @@ import { html } from "lit"; | ||||
|  | ||||
| import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "./elements/router/Route"; | ||||
| import "./pages/admin-overview/AdminOverviewPage"; | ||||
| import "./pages/admin-overview/DashboardUserPage"; | ||||
| import "./pages/applications/ApplicationListPage"; | ||||
| import "./pages/applications/ApplicationViewPage"; | ||||
| import "./pages/crypto/CertificateKeyPairListPage"; | ||||
| import "./pages/events/EventInfoPage"; | ||||
| import "./pages/events/EventListPage"; | ||||
| import "./pages/events/RuleListPage"; | ||||
| import "./pages/events/TransportListPage"; | ||||
| import "./pages/flows/FlowListPage"; | ||||
| import "./pages/flows/FlowViewPage"; | ||||
| import "./pages/groups/GroupListPage"; | ||||
| import "./pages/groups/GroupViewPage"; | ||||
| import "./pages/outposts/OutpostListPage"; | ||||
| import "./pages/outposts/ServiceConnectionListPage"; | ||||
| import "./pages/policies/PolicyListPage"; | ||||
| import "./pages/policies/reputation/ReputationListPage"; | ||||
| import "./pages/property-mappings/PropertyMappingListPage"; | ||||
| import "./pages/providers/ProviderListPage"; | ||||
| import "./pages/providers/ProviderViewPage"; | ||||
| import "./pages/sources/SourceListPage"; | ||||
| import "./pages/sources/SourceViewPage"; | ||||
| import "./pages/stages/StageListPage"; | ||||
| import "./pages/stages/invitation/InvitationListPage"; | ||||
| import "./pages/stages/prompt/PromptListPage"; | ||||
| import "./pages/system-tasks/SystemTaskListPage"; | ||||
| import "./pages/tenants/TenantListPage"; | ||||
| import "./pages/tokens/TokenListPage"; | ||||
| import "./pages/users/UserListPage"; | ||||
| import "./pages/users/UserViewPage"; | ||||
|  | ||||
| export const ROUTES: Route[] = [ | ||||
|     // Prevent infinite Shell loops | ||||
|     new Route(new RegExp("^/$")).redirect("/administration/overview"), | ||||
|     new Route(new RegExp("^#.*")).redirect("/administration/overview"), | ||||
|     new Route(new RegExp("^/library$")).redirectRaw("/if/user/"), | ||||
|     new Route( | ||||
|         new RegExp("^/administration/overview$"), | ||||
|         html`<ak-admin-overview></ak-admin-overview>`, | ||||
|     ), | ||||
|     new Route( | ||||
|         new RegExp("^/administration/dashboard/users$"), | ||||
|         html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`, | ||||
|     ), | ||||
|     new Route( | ||||
|         new RegExp("^/administration/system-tasks$"), | ||||
|         html`<ak-system-task-list></ak-system-task-list>`, | ||||
|     ), | ||||
|     new Route(new RegExp("^/core/providers$"), html`<ak-provider-list></ak-provider-list>`), | ||||
|     new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`)).then((args) => { | ||||
|     new Route(new RegExp("^/library$")).redirect("/if/user/", true), | ||||
|     // statically imported since this is the default route | ||||
|     new Route(new RegExp("^/administration/overview$"), async () => { | ||||
|         return html`<ak-admin-overview></ak-admin-overview>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/administration/dashboard/users$"), async () => { | ||||
|         await import("./pages/admin-overview/DashboardUserPage"); | ||||
|         return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/administration/system-tasks$"), async () => { | ||||
|         await import("./pages/system-tasks/SystemTaskListPage"); | ||||
|         return html`<ak-system-task-list></ak-system-task-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/providers$"), async () => { | ||||
|         await import("./pages/providers/ProviderListPage"); | ||||
|         return html`<ak-provider-list></ak-provider-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`), async (args) => { | ||||
|         await import("./pages/providers/ProviderViewPage"); | ||||
|         return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`; | ||||
|     }), | ||||
|     new Route( | ||||
|         new RegExp("^/core/applications$"), | ||||
|         html`<ak-application-list></ak-application-list>`, | ||||
|     ), | ||||
|     new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`)).then((args) => { | ||||
|     new Route(new RegExp("^/core/applications$"), async () => { | ||||
|         await import("./pages/applications/ApplicationListPage"); | ||||
|         return html`<ak-application-list></ak-application-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`), async (args) => { | ||||
|         await import("./pages/applications/ApplicationViewPage"); | ||||
|         return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/sources$"), html`<ak-source-list></ak-source-list>`), | ||||
|     new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`)).then((args) => { | ||||
|     new Route(new RegExp("^/core/sources$"), async () => { | ||||
|         await import("./pages/sources/SourceListPage"); | ||||
|         return html`<ak-source-list></ak-source-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`), async (args) => { | ||||
|         await import("./pages/sources/SourceViewPage"); | ||||
|         return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`; | ||||
|     }), | ||||
|     new Route( | ||||
|         new RegExp("^/core/property-mappings$"), | ||||
|         html`<ak-property-mapping-list></ak-property-mapping-list>`, | ||||
|     ), | ||||
|     new Route(new RegExp("^/core/tokens$"), html`<ak-token-list></ak-token-list>`), | ||||
|     new Route(new RegExp("^/core/tenants$"), html`<ak-tenant-list></ak-tenant-list>`), | ||||
|     new Route(new RegExp("^/policy/policies$"), html`<ak-policy-list></ak-policy-list>`), | ||||
|     new Route( | ||||
|         new RegExp("^/policy/reputation$"), | ||||
|         html`<ak-policy-reputation-list></ak-policy-reputation-list>`, | ||||
|     ), | ||||
|     new Route(new RegExp("^/identity/groups$"), html`<ak-group-list></ak-group-list>`), | ||||
|     new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`)).then((args) => { | ||||
|     new Route(new RegExp("^/core/property-mappings$"), async () => { | ||||
|         await import("./pages/property-mappings/PropertyMappingListPage"); | ||||
|         return html`<ak-property-mapping-list></ak-property-mapping-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/tokens$"), async () => { | ||||
|         await import("./pages/tokens/TokenListPage"); | ||||
|         return html`<ak-token-list></ak-token-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/tenants$"), async () => { | ||||
|         await import("./pages/tenants/TenantListPage"); | ||||
|         return html`<ak-tenant-list></ak-tenant-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/policy/policies$"), async () => { | ||||
|         await import("./pages/policies/PolicyListPage"); | ||||
|         return html`<ak-policy-list></ak-policy-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/policy/reputation$"), async () => { | ||||
|         await import("./pages/policies/reputation/ReputationListPage"); | ||||
|         return html`<ak-policy-reputation-list></ak-policy-reputation-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/identity/groups$"), async () => { | ||||
|         await import("./pages/groups/GroupListPage"); | ||||
|         return html`<ak-group-list></ak-group-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`), async (args) => { | ||||
|         await import("./pages/groups/GroupViewPage"); | ||||
|         return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/identity/users$"), html`<ak-user-list></ak-user-list>`), | ||||
|     new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`)).then((args) => { | ||||
|     new Route(new RegExp("^/identity/users$"), async () => { | ||||
|         await import("./pages/users/UserListPage"); | ||||
|         return html`<ak-user-list></ak-user-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`), async (args) => { | ||||
|         await import("./pages/users/UserViewPage"); | ||||
|         return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`; | ||||
|     }), | ||||
|     new Route( | ||||
|         new RegExp("^/flow/stages/invitations$"), | ||||
|         html`<ak-stage-invitation-list></ak-stage-invitation-list>`, | ||||
|     ), | ||||
|     new Route( | ||||
|         new RegExp("^/flow/stages/prompts$"), | ||||
|         html`<ak-stage-prompt-list></ak-stage-prompt-list>`, | ||||
|     ), | ||||
|     new Route(new RegExp("^/flow/stages$"), html`<ak-stage-list></ak-stage-list>`), | ||||
|     new Route(new RegExp("^/flow/flows$"), html`<ak-flow-list></ak-flow-list>`), | ||||
|     new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => { | ||||
|     new Route(new RegExp("^/flow/stages/invitations$"), async () => { | ||||
|         await import("./pages/stages/invitation/InvitationListPage"); | ||||
|         return html`<ak-stage-invitation-list></ak-stage-invitation-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/flow/stages/prompts$"), async () => { | ||||
|         await import("./pages/stages/prompt/PromptListPage"); | ||||
|         return html`<ak-stage-prompt-list></ak-stage-prompt-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/flow/stages$"), async () => { | ||||
|         await import("./pages/stages/StageListPage"); | ||||
|         return html`<ak-stage-list></ak-stage-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/flow/flows$"), async () => { | ||||
|         await import("./pages/flows/FlowListPage"); | ||||
|         return html`<ak-flow-list></ak-flow-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`), async (args) => { | ||||
|         await import("./pages/flows/FlowViewPage"); | ||||
|         return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/events/log$"), html`<ak-event-list></ak-event-list>`), | ||||
|     new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`)).then((args) => { | ||||
|     new Route(new RegExp("^/events/log$"), async () => { | ||||
|         await import("./pages/events/EventListPage"); | ||||
|         return html`<ak-event-list></ak-event-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`), async (args) => { | ||||
|         await import("./pages/events/EventInfoPage"); | ||||
|         return html`<ak-event-info-page .eventID=${args.id}></ak-event-info-page>`; | ||||
|     }), | ||||
|     new Route( | ||||
|         new RegExp("^/events/transports$"), | ||||
|         html`<ak-event-transport-list></ak-event-transport-list>`, | ||||
|     ), | ||||
|     new Route(new RegExp("^/events/rules$"), html`<ak-event-rule-list></ak-event-rule-list>`), | ||||
|     new Route(new RegExp("^/outpost/outposts$"), html`<ak-outpost-list></ak-outpost-list>`), | ||||
|     new Route( | ||||
|         new RegExp("^/outpost/integrations$"), | ||||
|         html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`, | ||||
|     ), | ||||
|     new Route( | ||||
|         new RegExp("^/crypto/certificates$"), | ||||
|         html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`, | ||||
|     ), | ||||
|     new Route(new RegExp("^/events/transports$"), async () => { | ||||
|         await import("./pages/events/TransportListPage"); | ||||
|         return html`<ak-event-transport-list></ak-event-transport-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/events/rules$"), async () => { | ||||
|         await import("./pages/events/RuleListPage"); | ||||
|         return html`<ak-event-rule-list></ak-event-rule-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/outpost/outposts$"), async () => { | ||||
|         await import("./pages/outposts/OutpostListPage"); | ||||
|         return html`<ak-outpost-list></ak-outpost-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/outpost/integrations$"), async () => { | ||||
|         await import("./pages/outposts/ServiceConnectionListPage"); | ||||
|         return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/crypto/certificates$"), async () => { | ||||
|         await import("./pages/crypto/CertificateKeyPairListPage"); | ||||
|         return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`; | ||||
|     }), | ||||
| ]; | ||||
|  | ||||
| @ -2,12 +2,14 @@ import { html } from "lit"; | ||||
|  | ||||
| import { Route } from "./elements/router/Route"; | ||||
| import "./user/LibraryPage"; | ||||
| import "./user/user-settings/UserSettingsPage"; | ||||
|  | ||||
| export const ROUTES: Route[] = [ | ||||
|     // Prevent infinite Shell loops | ||||
|     new Route(new RegExp("^/$")).redirect("/library"), | ||||
|     new Route(new RegExp("^#.*")).redirect("/library"), | ||||
|     new Route(new RegExp("^/library$"), html`<ak-library></ak-library>`), | ||||
|     new Route(new RegExp("^/settings$"), html`<ak-user-settings></ak-user-settings>`), | ||||
|     new Route(new RegExp("^/library$"), async () => html`<ak-library></ak-library>`), | ||||
|     new Route(new RegExp("^/settings$"), async () => { | ||||
|         await import("./user/user-settings/UserSettingsPage"); | ||||
|         return html`<ak-user-settings></ak-user-settings>`; | ||||
|     }), | ||||
| ]; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L