web: lazy load parts of interfaces (#2864)

This commit is contained in:
Jens L 2022-05-14 17:07:37 +02:00 committed by GitHub
parent 4da350ebfc
commit be06adcb59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 389 additions and 314 deletions

View File

@ -70,24 +70,32 @@ export function manualChunks(id) {
} }
} }
export const PLUGINS = [ export const defaultOptions = {
cssimport(), plugins: [
markdown(), cssimport(),
nodeResolve({ extensions, browser: true }), markdown(),
commonjs(), nodeResolve({ extensions, browser: true }),
babel({ commonjs(),
extensions, babel({
babelHelpers: "runtime", extensions,
include: ["src/**/*"], babelHelpers: "runtime",
}), include: ["src/**/*"],
replace({ }),
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"), replace({
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath), "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
"preventAssignment": true, "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
}), "preventAssignment": true,
sourcemaps(), }),
isProdBuild && terser(), sourcemaps(),
].filter((p) => p); isProdBuild && terser(),
].filter((p) => p),
watch: {
clearScreen: false,
},
preserveEntrySignatures: false,
cache: true,
context: "window",
};
// Polyfills (imported first) // Polyfills (imported first)
export const POLY = { export const POLY = {
@ -110,9 +118,6 @@ export const POLY = {
copyOnce: false, copyOnce: false,
}), }),
].filter((p) => p), ].filter((p) => p),
watch: {
clearScreen: false,
},
}; };
export default [ export default [
@ -120,8 +125,6 @@ export default [
// Flow interface // Flow interface
{ {
input: "./src/interfaces/FlowInterface.ts", input: "./src/interfaces/FlowInterface.ts",
context: "window",
cache: true,
output: [ output: [
{ {
format: "es", format: "es",
@ -130,16 +133,11 @@ export default [
manualChunks: manualChunks, manualChunks: manualChunks,
}, },
], ],
plugins: PLUGINS, ...defaultOptions,
watch: {
clearScreen: false,
},
}, },
// Admin interface // Admin interface
{ {
input: "./src/interfaces/AdminInterface.ts", input: "./src/interfaces/AdminInterface.ts",
context: "window",
cache: true,
output: [ output: [
{ {
format: "es", format: "es",
@ -148,16 +146,11 @@ export default [
manualChunks: manualChunks, manualChunks: manualChunks,
}, },
], ],
plugins: PLUGINS, ...defaultOptions,
watch: {
clearScreen: false,
},
}, },
// User interface // User interface
{ {
input: "./src/interfaces/UserInterface.ts", input: "./src/interfaces/UserInterface.ts",
context: "window",
cache: true,
output: [ output: [
{ {
format: "es", format: "es",
@ -166,9 +159,6 @@ export default [
manualChunks: manualChunks, manualChunks: manualChunks,
}, },
], ],
plugins: PLUGINS, ...defaultOptions,
watch: {
clearScreen: false,
},
}, },
]; ];

View File

@ -95,6 +95,11 @@ html > form > input {
vertical-align: middle; vertical-align: middle;
} }
.pf-c-description-list__description .pf-c-button {
margin-right: 6px;
margin-bottom: 6px;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.ak-static-page h1 { .ak-static-page h1 {
color: var(--ak-dark-foreground); color: var(--ak-dark-foreground);

View File

@ -1,4 +1,5 @@
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
import { until } from "lit/directives/until.js";
export const SLUG_REGEX = "[-a-zA-Z0-9_]+"; export const SLUG_REGEX = "[-a-zA-Z0-9_]+";
export const ID_REGEX = "\\d+"; export const ID_REGEX = "\\d+";
@ -12,39 +13,41 @@ export class Route {
url: RegExp; url: RegExp;
private element?: TemplateResult; 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.url = url;
this.element = element; this.callback = callback;
} }
redirect(to: string): Route { redirect(to: string, raw = false): Route {
this.callback = () => { this.callback = async () => {
console.debug(`authentik/router: redirecting ${to}`); console.debug(`authentik/router: redirecting ${to}`);
window.location.hash = `#${to}`; if (!raw) {
return html``; window.location.hash = `#${to}`;
}; } else {
return this; window.location.hash = to;
} }
redirectRaw(to: string): Route {
this.callback = () => {
console.debug(`authentik/router: redirecting ${to}`);
window.location.hash = `${to}`;
return html``; return html``;
}; };
return this; return this;
} }
then(render: (args: RouteArgs) => TemplateResult): Route { 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; this.callback = render;
return this; return this;
} }
render(args: RouteArgs): TemplateResult { render(args: RouteArgs): TemplateResult {
if (this.callback) { if (this.callback) {
return this.callback(args); return html`${until(this.callback(args))}`;
} }
if (this.element) { if (this.element) {
return this.element; return this.element;

View File

@ -30,26 +30,13 @@ import { WebsocketClient } from "../common/ws";
import { EVENT_FLOW_ADVANCE, TITLE_DEFAULT } from "../constants"; import { EVENT_FLOW_ADVANCE, TITLE_DEFAULT } from "../constants";
import "../elements/LoadingOverlay"; import "../elements/LoadingOverlay";
import { first } from "../utils"; import { first } from "../utils";
import "./FlowInspector";
import "./sources/apple/AppleLoginInit";
import "./sources/plex/PlexLoginInit";
import "./stages/RedirectStage"; import "./stages/RedirectStage";
import "./stages/access_denied/AccessDeniedStage"; 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 "./stages/autosubmit/AutosubmitStage";
import { StageHost } from "./stages/base"; import { StageHost } from "./stages/base";
import "./stages/captcha/CaptchaStage"; import "./stages/captcha/CaptchaStage";
import "./stages/consent/ConsentStage";
import "./stages/dummy/DummyStage";
import "./stages/email/EmailStage";
import "./stages/identification/IdentificationStage"; import "./stages/identification/IdentificationStage";
import "./stages/password/PasswordStage"; import "./stages/password/PasswordStage";
import "./stages/prompt/PromptStage";
@customElement("ak-flow-executor") @customElement("ak-flow-executor")
export class FlowExecutor extends LitElement implements StageHost { export class FlowExecutor extends LitElement implements StageHost {
@ -229,7 +216,117 @@ export class FlowExecutor extends LitElement implements StageHost {
} as ChallengeTypes; } 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) { if (!this.challenge) {
return html``; return html``;
} }
@ -247,96 +344,7 @@ export class FlowExecutor extends LitElement implements StageHost {
case ChallengeChoices.Shell: case ChallengeChoices.Shell:
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`; return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
case ChallengeChoices.Native: case ChallengeChoices.Native:
switch (this.challenge.component) { return await this.renderChallengeNativeElement();
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;
default: default:
console.debug(`authentik/flows: unexpected data type ${this.challenge.type}`); console.debug(`authentik/flows: unexpected data type ${this.challenge.type}`);
break; break;
@ -350,10 +358,20 @@ export class FlowExecutor extends LitElement implements StageHost {
} }
return html` return html`
${this.loading ? html`<ak-loading-overlay></ak-loading-overlay>` : 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 { render(): TemplateResult {
return html`<div class="pf-c-background-image"> return html`<div class="pf-c-background-image">
<svg <svg
@ -439,13 +457,7 @@ export class FlowExecutor extends LitElement implements StageHost {
</div> </div>
</div> </div>
</div> </div>
${until(this.renderInspector())}
<ak-flow-inspector
class="pf-c-drawer__panel pf-m-width-33 ${this.inspectorOpen
? ""
: "display-none"}"
?hidden=${!this.inspectorOpen}
></ak-flow-inspector>
</div> </div>
</div> </div>
</div>`; </div>`;

View File

@ -1,96 +1,122 @@
import { de, en, es, fr, pl, tr, zh } from "make-plural/plurals";
import { Messages, i18n } from "@lingui/core"; import { Messages, i18n } from "@lingui/core";
import { detect, fromNavigator, fromUrl } from "@lingui/detect-locale"; import { detect, fromNavigator, fromUrl } from "@lingui/detect-locale";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { LitElement } from "lit"; import { LitElement } from "lit";
import { messages as localeDE } from "../locales/de"; interface Locale {
import { messages as localeEN } from "../locales/en"; locale: Messages;
import { messages as localeES } from "../locales/es"; // eslint-disable-next-line @typescript-eslint/ban-types
import { messages as localeFR_FR } from "../locales/fr_FR"; plurals: Function;
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";
export const LOCALES: { export const LOCALES: {
code: string; code: string;
label: string; label: string;
// eslint-disable-next-line @typescript-eslint/ban-types locale: () => Promise<Locale>;
plurals: Function;
locale: Messages;
}[] = [ }[] = [
{ {
code: "en", code: "en",
plurals: en,
label: t`English`, label: t`English`,
locale: localeEN, locale: async () => {
return {
locale: (await import("../locales/en")).messages,
plurals: (await import("make-plural/plurals")).en,
};
},
}, },
{ {
code: "debug", code: "debug",
plurals: en,
label: t`Debug`, label: t`Debug`,
locale: localeDEBUG, locale: async () => {
return {
locale: (await import("../locales/pseudo-LOCALE")).messages,
plurals: (await import("make-plural/plurals")).en,
};
},
}, },
{ {
code: "fr", code: "fr",
plurals: fr,
label: t`French`, 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", code: "tr",
plurals: tr,
label: t`Turkish`, label: t`Turkish`,
locale: localeTR, locale: async () => {
return {
locale: (await import("../locales/tr")).messages,
plurals: (await import("make-plural/plurals")).tr,
};
},
}, },
{ {
code: "es", code: "es",
plurals: es,
label: t`Spanish`, label: t`Spanish`,
locale: localeES, locale: async () => {
return {
locale: (await import("../locales/es")).messages,
plurals: (await import("make-plural/plurals")).es,
};
},
}, },
{ {
code: "pl", code: "pl",
plurals: pl,
label: t`Polish`, label: t`Polish`,
locale: localePL, locale: async () => {
return {
locale: (await import("../locales/pl")).messages,
plurals: (await import("make-plural/plurals")).pl,
};
},
}, },
{ {
code: "zh_TW", code: "zh_TW",
plurals: zh,
label: t`Taiwanese Mandarin`, 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", code: "zh-CN",
plurals: zh,
label: t`Chinese (simplified)`, 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", code: "zh-HK",
plurals: zh,
label: t`Chinese (traditional)`, 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", code: "de",
plurals: de,
label: t`German`, 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"; const DEFAULT_FALLBACK = () => "en";
export function autoDetectLanguage() { export function autoDetectLanguage() {
@ -102,25 +128,33 @@ export function autoDetectLanguage() {
} }
if (detected in i18n._messages) { if (detected in i18n._messages) {
console.debug(`authentik/locale: Activating detected locale '${detected}'`); console.debug(`authentik/locale: Activating detected locale '${detected}'`);
i18n.activate(detected); activateLocale(detected);
} else { } else {
console.debug(`authentik/locale: No locale for '${detected}', falling back to en`); console.debug(`authentik/locale: No locale for '${detected}', falling back to en`);
i18n.activate(DEFAULT_FALLBACK()); activateLocale(DEFAULT_FALLBACK());
} }
} }
export function activateLocale(code: string) { export function activateLocale(code: string) {
const urlLocale = fromUrl("locale"); const urlLocale = fromUrl("locale");
if (urlLocale !== null && urlLocale !== "") { if (urlLocale !== null && urlLocale !== "") {
i18n.activate(urlLocale); code = urlLocale;
} else {
i18n.activate(code);
} }
document.querySelectorAll("[data-refresh-on-locale=true]").forEach((el) => { const locale = LOCALES.find((locale) => locale.code == code);
try { if (!locale) {
(el as LitElement).requestUpdate(); console.warn(`authentik/locale: failed to find locale for code ${code}`);
} catch { return;
console.debug(`authentik/locale: failed to update element ${el}`); }
} 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(); autoDetectLanguage();

View File

@ -2824,6 +2824,7 @@ msgstr "Server laden"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2876,6 +2876,7 @@ msgstr "Load servers"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2817,6 +2817,7 @@ msgstr "Servidores de carga"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2848,6 +2848,7 @@ msgstr "Charger les serveurs"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2814,6 +2814,7 @@ msgstr "Załaduj serwery"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2858,6 +2858,7 @@ msgstr ""
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2818,6 +2818,7 @@ msgstr "Sunucuları yükle"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2802,6 +2802,7 @@ msgstr "加载服务器"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2804,6 +2804,7 @@ msgstr "加载服务器"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -2804,6 +2804,7 @@ msgstr "加载服务器"
#: src/elements/table/Table.ts #: src/elements/table/Table.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts #: src/flows/FlowExecutor.ts
#: src/flows/FlowExecutor.ts
#: src/flows/FlowInspector.ts #: src/flows/FlowInspector.ts
#: src/flows/stages/access_denied/AccessDeniedStage.ts #: src/flows/stages/access_denied/AccessDeniedStage.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts #: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts

View File

@ -163,6 +163,11 @@ export class FlowViewPage extends LitElement {
`inspector&next=/#${window.location.hash}`, `inspector&next=/#${window.location.hash}`,
)}`; )}`;
window.open(finalURL, "_blank"); 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");
}); });
}} }}
> >

View File

@ -2,116 +2,130 @@ import { html } from "lit";
import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "./elements/router/Route"; import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "./elements/router/Route";
import "./pages/admin-overview/AdminOverviewPage"; 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[] = [ export const ROUTES: Route[] = [
// Prevent infinite Shell loops // Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/administration/overview"), new Route(new RegExp("^/$")).redirect("/administration/overview"),
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("^/library$")).redirect("/if/user/", true),
new Route( // statically imported since this is the default route
new RegExp("^/administration/overview$"), new Route(new RegExp("^/administration/overview$"), async () => {
html`<ak-admin-overview></ak-admin-overview>`, return html`<ak-admin-overview></ak-admin-overview>`;
), }),
new Route( new Route(new RegExp("^/administration/dashboard/users$"), async () => {
new RegExp("^/administration/dashboard/users$"), await import("./pages/admin-overview/DashboardUserPage");
html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`, return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
), }),
new Route( new Route(new RegExp("^/administration/system-tasks$"), async () => {
new RegExp("^/administration/system-tasks$"), await import("./pages/system-tasks/SystemTaskListPage");
html`<ak-system-task-list></ak-system-task-list>`, return 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$"), async () => {
new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`)).then((args) => { 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>`; return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`;
}), }),
new Route( new Route(new RegExp("^/core/applications$"), async () => {
new RegExp("^/core/applications$"), await import("./pages/applications/ApplicationListPage");
html`<ak-application-list></ak-application-list>`, return html`<ak-application-list></ak-application-list>`;
), }),
new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`)).then((args) => { 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>`; 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$"), async () => {
new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`)).then((args) => { 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>`; return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`;
}), }),
new Route( new Route(new RegExp("^/core/property-mappings$"), async () => {
new RegExp("^/core/property-mappings$"), await import("./pages/property-mappings/PropertyMappingListPage");
html`<ak-property-mapping-list></ak-property-mapping-list>`, return 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/tokens$"), async () => {
new Route(new RegExp("^/core/tenants$"), html`<ak-tenant-list></ak-tenant-list>`), await import("./pages/tokens/TokenListPage");
new Route(new RegExp("^/policy/policies$"), html`<ak-policy-list></ak-policy-list>`), return html`<ak-token-list></ak-token-list>`;
new Route( }),
new RegExp("^/policy/reputation$"), new Route(new RegExp("^/core/tenants$"), async () => {
html`<ak-policy-reputation-list></ak-policy-reputation-list>`, await import("./pages/tenants/TenantListPage");
), return html`<ak-tenant-list></ak-tenant-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("^/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>`; 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$"), async () => {
new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`)).then((args) => { 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>`; return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`;
}), }),
new Route( new Route(new RegExp("^/flow/stages/invitations$"), async () => {
new RegExp("^/flow/stages/invitations$"), await import("./pages/stages/invitation/InvitationListPage");
html`<ak-stage-invitation-list></ak-stage-invitation-list>`, return html`<ak-stage-invitation-list></ak-stage-invitation-list>`;
), }),
new Route( new Route(new RegExp("^/flow/stages/prompts$"), async () => {
new RegExp("^/flow/stages/prompts$"), await import("./pages/stages/prompt/PromptListPage");
html`<ak-stage-prompt-list></ak-stage-prompt-list>`, return 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/stages$"), async () => {
new Route(new RegExp("^/flow/flows$"), html`<ak-flow-list></ak-flow-list>`), await import("./pages/stages/StageListPage");
new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => { 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>`; 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$"), async () => {
new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`)).then((args) => { 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>`; return html`<ak-event-info-page .eventID=${args.id}></ak-event-info-page>`;
}), }),
new Route( new Route(new RegExp("^/events/transports$"), async () => {
new RegExp("^/events/transports$"), await import("./pages/events/TransportListPage");
html`<ak-event-transport-list></ak-event-transport-list>`, return 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("^/events/rules$"), async () => {
new Route(new RegExp("^/outpost/outposts$"), html`<ak-outpost-list></ak-outpost-list>`), await import("./pages/events/RuleListPage");
new Route( return html`<ak-event-rule-list></ak-event-rule-list>`;
new RegExp("^/outpost/integrations$"), }),
html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`, new Route(new RegExp("^/outpost/outposts$"), async () => {
), await import("./pages/outposts/OutpostListPage");
new Route( return html`<ak-outpost-list></ak-outpost-list>`;
new RegExp("^/crypto/certificates$"), }),
html`<ak-crypto-certificate-list></ak-crypto-certificate-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>`;
}),
]; ];

View File

@ -2,12 +2,14 @@ import { html } from "lit";
import { Route } from "./elements/router/Route"; import { Route } from "./elements/router/Route";
import "./user/LibraryPage"; import "./user/LibraryPage";
import "./user/user-settings/UserSettingsPage";
export const ROUTES: Route[] = [ export const ROUTES: Route[] = [
// Prevent infinite Shell loops // Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/library"), new Route(new RegExp("^/$")).redirect("/library"),
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("^/library$"), async () => html`<ak-library></ak-library>`),
new Route(new RegExp("^/settings$"), html`<ak-user-settings></ak-user-settings>`), new Route(new RegExp("^/settings$"), async () => {
await import("./user/user-settings/UserSettingsPage");
return html`<ak-user-settings></ak-user-settings>`;
}),
]; ];