web: lazy load parts of interfaces (#2864)
This commit is contained in:
parent
4da350ebfc
commit
be06adcb59
|
@ -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