web: refactor locale handler into top-level context handler (#6022)
* web: begin refactoring the application for future development This commit: - Deletes a bit of code. - Extracts *all* of the Locale logic into a single folder, turns management of the Locale files over to Lit itself, and restricts our responsibility to setting the locale on startup and when the user changes the locale. We do this by converting a lot of internal calls into events; a request to change a locale isn't a function call, it's an event emitted asking `REQUEST_LOCALE_CHANGE`. We've even eliminated the `DETECT_LOCALE_CHANGE` event, which redrew elements with text in them, since Lit's own `@localized()` decorator does that for us automagically. - We wrap our interfaces in an `ak-locale-context` that handles the startup and listens for the `REQUEST_LOCALE_CHANGE` event. - ... and that's pretty much it. Adding `@localized()` as a default behavior to `AKElement` means no more custom localization is needed *anywhere*. * web: improve the localization experience This commit fixes the Storybook story for the localization context component, and fixes the localization initialization pass so that it is only called once per interface environment initialization. Since all our interfaces share the same environment (the Django server), this preserves functionality across all interfaces. --------- Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		
							
								
								
									
										442
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										442
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -18,6 +18,7 @@ | ||||
|                 "@formatjs/intl-listformat": "^7.4.0", | ||||
|                 "@fortawesome/fontawesome-free": "^6.4.0", | ||||
|                 "@goauthentik/api": "^2023.6.0-1688736781", | ||||
|                 "@lit-labs/context": "^0.3.3", | ||||
|                 "@lit/localize": "^0.11.4", | ||||
|                 "@patternfly/patternfly": "^4.224.2", | ||||
|                 "@sentry/browser": "^7.57.0", | ||||
| @ -2531,54 +2532,6 @@ | ||||
|                 "react": ">=16.8.0" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/android-arm": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", | ||||
|             "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", | ||||
|             "cpu": [ | ||||
|                 "arm" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "android" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/android-arm64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", | ||||
|             "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "android" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/android-x64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", | ||||
|             "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "android" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/darwin-arm64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", | ||||
| @ -2595,294 +2548,6 @@ | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/darwin-x64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", | ||||
|             "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "darwin" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/freebsd-arm64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", | ||||
|             "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "freebsd" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/freebsd-x64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", | ||||
|             "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "freebsd" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-arm": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", | ||||
|             "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", | ||||
|             "cpu": [ | ||||
|                 "arm" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-arm64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", | ||||
|             "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-ia32": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", | ||||
|             "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", | ||||
|             "cpu": [ | ||||
|                 "ia32" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-loong64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", | ||||
|             "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", | ||||
|             "cpu": [ | ||||
|                 "loong64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-mips64el": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", | ||||
|             "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", | ||||
|             "cpu": [ | ||||
|                 "mips64el" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-ppc64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", | ||||
|             "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", | ||||
|             "cpu": [ | ||||
|                 "ppc64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-riscv64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", | ||||
|             "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", | ||||
|             "cpu": [ | ||||
|                 "riscv64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-s390x": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", | ||||
|             "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", | ||||
|             "cpu": [ | ||||
|                 "s390x" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/linux-x64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", | ||||
|             "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "linux" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/netbsd-x64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", | ||||
|             "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "netbsd" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/openbsd-x64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", | ||||
|             "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "openbsd" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/sunos-x64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", | ||||
|             "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "sunos" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/win32-arm64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", | ||||
|             "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "win32" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/win32-ia32": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", | ||||
|             "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", | ||||
|             "cpu": [ | ||||
|                 "ia32" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "win32" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@esbuild/win32-x64": { | ||||
|             "version": "0.17.19", | ||||
|             "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", | ||||
|             "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
|             "dev": true, | ||||
|             "optional": true, | ||||
|             "os": [ | ||||
|                 "win32" | ||||
|             ], | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@eslint-community/eslint-utils": { | ||||
|             "version": "4.2.0", | ||||
|             "dev": true, | ||||
| @ -3164,22 +2829,22 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@jest/transform": { | ||||
|             "version": "29.6.0", | ||||
|             "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.0.tgz", | ||||
|             "integrity": "sha512-bhP/KxPo3e322FJ0nKAcb6WVK76ZYyQd1lWygJzoSqP8SYMSLdxHqP4wnPTI4WvbB8PKPDV30y5y7Tya4RHOBA==", | ||||
|             "version": "29.6.1", | ||||
|             "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.1.tgz", | ||||
|             "integrity": "sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@babel/core": "^7.11.6", | ||||
|                 "@jest/types": "^29.6.0", | ||||
|                 "@jest/types": "^29.6.1", | ||||
|                 "@jridgewell/trace-mapping": "^0.3.18", | ||||
|                 "babel-plugin-istanbul": "^6.1.1", | ||||
|                 "chalk": "^4.0.0", | ||||
|                 "convert-source-map": "^2.0.0", | ||||
|                 "fast-json-stable-stringify": "^2.1.0", | ||||
|                 "graceful-fs": "^4.2.9", | ||||
|                 "jest-haste-map": "^29.6.0", | ||||
|                 "jest-haste-map": "^29.6.1", | ||||
|                 "jest-regex-util": "^29.4.3", | ||||
|                 "jest-util": "^29.6.0", | ||||
|                 "jest-util": "^29.6.1", | ||||
|                 "micromatch": "^4.0.4", | ||||
|                 "pirates": "^4.0.4", | ||||
|                 "slash": "^3.0.0", | ||||
| @ -3266,9 +2931,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@jest/types": { | ||||
|             "version": "29.6.0", | ||||
|             "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.0.tgz", | ||||
|             "integrity": "sha512-8XCgL9JhqbJTFnMRjEAO+TuW251+MoMd5BSzLiE3vvzpQ8RlBxy8NoyNkDhs3K3OL3HeVinlOl9or5p7GTeOLg==", | ||||
|             "version": "29.6.1", | ||||
|             "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", | ||||
|             "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@jest/schemas": "^29.6.0", | ||||
| @ -3498,6 +3163,15 @@ | ||||
|                 "@lezer/lr": "^1.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@lit-labs/context": { | ||||
|             "version": "0.3.3", | ||||
|             "resolved": "https://registry.npmjs.org/@lit-labs/context/-/context-0.3.3.tgz", | ||||
|             "integrity": "sha512-5pWPLiXJnx8fZREF4w7RXBwJOxqRBJ57tujo7k23s0ZDfnSltomvYGW4kTOurXfyzDR0OLBBkv9xsWGDhauqew==", | ||||
|             "dependencies": { | ||||
|                 "@lit/reactive-element": "^1.5.0", | ||||
|                 "lit": "^2.7.0" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@lit-labs/ssr-dom-shim": { | ||||
|             "version": "1.1.0", | ||||
|             "license": "BSD-3-Clause" | ||||
| @ -9703,9 +9377,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/acorn": { | ||||
|             "version": "8.9.0", | ||||
|             "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", | ||||
|             "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", | ||||
|             "version": "8.10.0", | ||||
|             "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", | ||||
|             "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", | ||||
|             "dev": true, | ||||
|             "bin": { | ||||
|                 "acorn": "bin/acorn" | ||||
| @ -11087,12 +10761,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/core-js-compat": { | ||||
|             "version": "3.31.0", | ||||
|             "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", | ||||
|             "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==", | ||||
|             "version": "3.31.1", | ||||
|             "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.1.tgz", | ||||
|             "integrity": "sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "browserslist": "^4.21.5" | ||||
|                 "browserslist": "^4.21.9" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -11992,9 +11666,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/electron-to-chromium": { | ||||
|             "version": "1.4.450", | ||||
|             "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.450.tgz", | ||||
|             "integrity": "sha512-BLG5HxSELlrMx7dJ2s+8SFlsCtJp37Zpk2VAxyC6CZtbc+9AJeZHfYHbrlSgdXp6saQ8StMqOTEDaBKgA7u1sw==", | ||||
|             "version": "1.4.451", | ||||
|             "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.451.tgz", | ||||
|             "integrity": "sha512-YYbXHIBxAHe3KWvGOJOuWa6f3tgow44rBW+QAuwVp2DvGqNZeE//K2MowNdWS7XE8li5cgQDrX1LdBr41LufkA==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "node_modules/elkjs": { | ||||
| @ -12943,8 +12617,9 @@ | ||||
|         }, | ||||
|         "node_modules/fast-json-stable-stringify": { | ||||
|             "version": "2.1.0", | ||||
|             "dev": true, | ||||
|             "license": "MIT" | ||||
|             "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", | ||||
|             "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "node_modules/fast-levenshtein": { | ||||
|             "version": "2.0.6", | ||||
| @ -14755,20 +14430,20 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/jest-haste-map": { | ||||
|             "version": "29.6.0", | ||||
|             "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.0.tgz", | ||||
|             "integrity": "sha512-dY1DKufptj7hcJSuhpqlYPGcnN3XjlOy/g0jinpRTMsbb40ivZHiuIPzeminOZkrek8C+oDxC54ILGO3vMLojg==", | ||||
|             "version": "29.6.1", | ||||
|             "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.1.tgz", | ||||
|             "integrity": "sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@jest/types": "^29.6.0", | ||||
|                 "@jest/types": "^29.6.1", | ||||
|                 "@types/graceful-fs": "^4.1.3", | ||||
|                 "@types/node": "*", | ||||
|                 "anymatch": "^3.0.3", | ||||
|                 "fb-watchman": "^2.0.0", | ||||
|                 "graceful-fs": "^4.2.9", | ||||
|                 "jest-regex-util": "^29.4.3", | ||||
|                 "jest-util": "^29.6.0", | ||||
|                 "jest-worker": "^29.6.0", | ||||
|                 "jest-util": "^29.6.1", | ||||
|                 "jest-worker": "^29.6.1", | ||||
|                 "micromatch": "^4.0.4", | ||||
|                 "walker": "^1.0.8" | ||||
|             }, | ||||
| @ -14789,13 +14464,13 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/jest-haste-map/node_modules/jest-worker": { | ||||
|             "version": "29.6.0", | ||||
|             "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.0.tgz", | ||||
|             "integrity": "sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA==", | ||||
|             "version": "29.6.1", | ||||
|             "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.1.tgz", | ||||
|             "integrity": "sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@types/node": "*", | ||||
|                 "jest-util": "^29.6.0", | ||||
|                 "jest-util": "^29.6.1", | ||||
|                 "merge-stream": "^2.0.0", | ||||
|                 "supports-color": "^8.0.0" | ||||
|             }, | ||||
| @ -14828,12 +14503,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/jest-util": { | ||||
|             "version": "29.6.0", | ||||
|             "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.0.tgz", | ||||
|             "integrity": "sha512-S0USx9YwcvEm4pQ5suisVm/RVxBmi0GFR7ocJhIeaCuW5AXnAnffXbaVKvIFodyZNOc9ygzVtTxmBf40HsHXaA==", | ||||
|             "version": "29.6.1", | ||||
|             "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", | ||||
|             "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@jest/types": "^29.6.0", | ||||
|                 "@jest/types": "^29.6.1", | ||||
|                 "@types/node": "*", | ||||
|                 "chalk": "^4.0.0", | ||||
|                 "ci-info": "^3.2.0", | ||||
| @ -15503,8 +15178,9 @@ | ||||
|         }, | ||||
|         "node_modules/lodash.debounce": { | ||||
|             "version": "4.0.8", | ||||
|             "dev": true, | ||||
|             "license": "MIT" | ||||
|             "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", | ||||
|             "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "node_modules/lodash.deburr": { | ||||
|             "version": "4.1.0", | ||||
| @ -16297,8 +15973,9 @@ | ||||
|         }, | ||||
|         "node_modules/mimic-fn": { | ||||
|             "version": "2.1.0", | ||||
|             "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", | ||||
|             "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
|                 "node": ">=6" | ||||
|             } | ||||
| @ -16680,8 +16357,9 @@ | ||||
|         }, | ||||
|         "node_modules/node-releases": { | ||||
|             "version": "2.0.12", | ||||
|             "dev": true, | ||||
|             "license": "MIT" | ||||
|             "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", | ||||
|             "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "node_modules/non-layered-tidy-tree-layout": { | ||||
|             "version": "2.0.2", | ||||
| @ -17023,8 +16701,9 @@ | ||||
|         }, | ||||
|         "node_modules/onetime": { | ||||
|             "version": "5.1.2", | ||||
|             "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", | ||||
|             "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "mimic-fn": "^2.1.0" | ||||
|             }, | ||||
| @ -17431,9 +17110,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/pirates": { | ||||
|             "version": "4.0.5", | ||||
|             "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", | ||||
|             "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", | ||||
|             "version": "4.0.6", | ||||
|             "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", | ||||
|             "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", | ||||
|             "dev": true, | ||||
|             "engines": { | ||||
|                 "node": ">= 6" | ||||
| @ -20586,6 +20265,8 @@ | ||||
|         }, | ||||
|         "node_modules/update-browserslist-db": { | ||||
|             "version": "1.0.11", | ||||
|             "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", | ||||
|             "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", | ||||
|             "dev": true, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @ -20601,7 +20282,6 @@ | ||||
|                     "url": "https://github.com/sponsors/ai" | ||||
|                 } | ||||
|             ], | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "escalade": "^3.1.1", | ||||
|                 "picocolors": "^1.0.0" | ||||
|  | ||||
| @ -16,6 +16,7 @@ | ||||
|         "watch": "run-s build-locales rollup:watch", | ||||
|         "lint": "eslint . --max-warnings 0 --fix", | ||||
|         "lit-analyse": "lit-analyzer src", | ||||
|         "precommit": "run-s tsc lit-analyse lint prettier", | ||||
|         "prettier-check": "prettier --check .", | ||||
|         "prettier": "prettier --write .", | ||||
|         "tsc:execute": "tsc --noEmit -p .", | ||||
| @ -33,6 +34,7 @@ | ||||
|         "@codemirror/theme-one-dark": "^6.1.2", | ||||
|         "@formatjs/intl-listformat": "^7.4.0", | ||||
|         "@fortawesome/fontawesome-free": "^6.4.0", | ||||
|         "@lit-labs/context": "^0.3.3", | ||||
|         "@goauthentik/api": "^2023.6.0-1688736781", | ||||
|         "@lit/localize": "^0.11.4", | ||||
|         "@patternfly/patternfly": "^4.224.2", | ||||
|  | ||||
| @ -7,10 +7,10 @@ import { | ||||
|     VERSION, | ||||
| } from "@goauthentik/common/constants"; | ||||
| import { configureSentry } from "@goauthentik/common/sentry"; | ||||
| import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; | ||||
| import { me } from "@goauthentik/common/users"; | ||||
| import { WebsocketClient } from "@goauthentik/common/ws"; | ||||
| import { Interface } from "@goauthentik/elements/Base"; | ||||
| import "@goauthentik/elements/ak-locale-context"; | ||||
| import "@goauthentik/elements/messages/MessageContainer"; | ||||
| import "@goauthentik/elements/messages/MessageContainer"; | ||||
| import "@goauthentik/elements/notifications/APIDrawer"; | ||||
| @ -32,8 +32,6 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
|  | ||||
| import { AdminApi, CoreApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api"; | ||||
|  | ||||
| autoDetectLanguage(); | ||||
|  | ||||
| @customElement("ak-interface-admin") | ||||
| export class AdminInterface extends Interface { | ||||
|     @property({ type: Boolean }) | ||||
| @ -114,54 +112,56 @@ export class AdminInterface extends Interface { | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html` <div class="pf-c-page"> | ||||
|             <ak-sidebar | ||||
|                 class="pf-c-page__sidebar ${this.sidebarOpen | ||||
|                     ? "pf-m-expanded" | ||||
|                     : "pf-m-collapsed"} ${this.activeTheme === UiThemeEnum.Light | ||||
|                     ? "pf-m-light" | ||||
|                     : ""}" | ||||
|             > | ||||
|                 ${this.renderSidebarItems()} | ||||
|             </ak-sidebar> | ||||
|             <div class="pf-c-page__drawer"> | ||||
|                 <div | ||||
|                     class="pf-c-drawer ${this.notificationDrawerOpen || this.apiDrawerOpen | ||||
|         return html` <ak-locale-context | ||||
|             ><div class="pf-c-page"> | ||||
|                 <ak-sidebar | ||||
|                     class="pf-c-page__sidebar ${this.sidebarOpen | ||||
|                         ? "pf-m-expanded" | ||||
|                         : "pf-m-collapsed"}" | ||||
|                         : "pf-m-collapsed"} ${this.activeTheme === UiThemeEnum.Light | ||||
|                         ? "pf-m-light" | ||||
|                         : ""}" | ||||
|                 > | ||||
|                     <div class="pf-c-drawer__main"> | ||||
|                         <div class="pf-c-drawer__content"> | ||||
|                             <div class="pf-c-drawer__body"> | ||||
|                                 <main class="pf-c-page__main"> | ||||
|                                     <ak-router-outlet | ||||
|                                         role="main" | ||||
|                                         class="pf-c-page__main" | ||||
|                                         tabindex="-1" | ||||
|                                         id="main-content" | ||||
|                                         defaultUrl="/administration/overview" | ||||
|                                         .routes=${ROUTES} | ||||
|                                     > | ||||
|                                     </ak-router-outlet> | ||||
|                                 </main> | ||||
|                     ${this.renderSidebarItems()} | ||||
|                 </ak-sidebar> | ||||
|                 <div class="pf-c-page__drawer"> | ||||
|                     <div | ||||
|                         class="pf-c-drawer ${this.notificationDrawerOpen || this.apiDrawerOpen | ||||
|                             ? "pf-m-expanded" | ||||
|                             : "pf-m-collapsed"}" | ||||
|                     > | ||||
|                         <div class="pf-c-drawer__main"> | ||||
|                             <div class="pf-c-drawer__content"> | ||||
|                                 <div class="pf-c-drawer__body"> | ||||
|                                     <main class="pf-c-page__main"> | ||||
|                                         <ak-router-outlet | ||||
|                                             role="main" | ||||
|                                             class="pf-c-page__main" | ||||
|                                             tabindex="-1" | ||||
|                                             id="main-content" | ||||
|                                             defaultUrl="/administration/overview" | ||||
|                                             .routes=${ROUTES} | ||||
|                                         > | ||||
|                                         </ak-router-outlet> | ||||
|                                     </main> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <ak-notification-drawer | ||||
|                                 class="pf-c-drawer__panel pf-m-width-33 ${this | ||||
|                                     .notificationDrawerOpen | ||||
|                                     ? "" | ||||
|                                     : "display-none"}" | ||||
|                                 ?hidden=${!this.notificationDrawerOpen} | ||||
|                             ></ak-notification-drawer> | ||||
|                             <ak-api-drawer | ||||
|                                 class="pf-c-drawer__panel pf-m-width-33 ${this.apiDrawerOpen | ||||
|                                     ? "" | ||||
|                                     : "display-none"}" | ||||
|                                 ?hidden=${!this.apiDrawerOpen} | ||||
|                             ></ak-api-drawer> | ||||
|                         </div> | ||||
|                         <ak-notification-drawer | ||||
|                             class="pf-c-drawer__panel pf-m-width-33 ${this.notificationDrawerOpen | ||||
|                                 ? "" | ||||
|                                 : "display-none"}" | ||||
|                             ?hidden=${!this.notificationDrawerOpen} | ||||
|                         ></ak-notification-drawer> | ||||
|                         <ak-api-drawer | ||||
|                             class="pf-c-drawer__panel pf-m-width-33 ${this.apiDrawerOpen | ||||
|                                 ? "" | ||||
|                                 : "display-none"}" | ||||
|                             ?hidden=${!this.apiDrawerOpen} | ||||
|                         ></ak-api-drawer> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div>`; | ||||
|                 </div></div | ||||
|         ></ak-locale-context>`; | ||||
|     } | ||||
|  | ||||
|     renderSidebarItems(): TemplateResult { | ||||
|  | ||||
| @ -3,9 +3,9 @@ import { | ||||
|     EventMiddleware, | ||||
|     LoggingMiddleware, | ||||
| } from "@goauthentik/common/api/middleware"; | ||||
| import { EVENT_REFRESH, VERSION } from "@goauthentik/common/constants"; | ||||
| import { EVENT_LOCALE_REQUEST, EVENT_REFRESH, VERSION } from "@goauthentik/common/constants"; | ||||
| import { globalAK } from "@goauthentik/common/global"; | ||||
| import { activateLocale } from "@goauthentik/common/ui/locale"; | ||||
| import { customEvent } from "@goauthentik/elements/utils/customEvents"; | ||||
|  | ||||
| import { Config, Configuration, CoreApi, CurrentTenant, RootApi } from "@goauthentik/api"; | ||||
|  | ||||
| @ -39,7 +39,7 @@ export function tenantSetLocale(tenant: CurrentTenant) { | ||||
|         return; | ||||
|     } | ||||
|     console.debug("authentik/locale: setting locale from tenant default"); | ||||
|     activateLocale(tenant.defaultLocale); | ||||
|     window.dispatchEvent(customEvent(EVENT_LOCALE_REQUEST, { locale: tenant.defaultLocale })); | ||||
| } | ||||
|  | ||||
| let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve(globalAK().tenant); | ||||
|  | ||||
| @ -15,6 +15,7 @@ export const EVENT_SIDEBAR_TOGGLE = "ak-sidebar-toggle"; | ||||
| export const EVENT_WS_MESSAGE = "ak-ws-message"; | ||||
| export const EVENT_FLOW_ADVANCE = "ak-flow-advance"; | ||||
| export const EVENT_LOCALE_CHANGE = "ak-locale-change"; | ||||
| export const EVENT_LOCALE_REQUEST = "ak-locale-request"; | ||||
| export const EVENT_REQUEST_POST = "ak-request-post"; | ||||
| export const EVENT_MESSAGE = "ak-message"; | ||||
| export const EVENT_THEME_CHANGE = "ak-theme-change"; | ||||
|  | ||||
| @ -1,26 +0,0 @@ | ||||
| import { getLocale, setLocale } from "./configureLocale"; | ||||
| import { getBestMatchLocale, localeFromUrl } from "./helpers"; | ||||
|  | ||||
| export function activateLocale(code: string) { | ||||
|     const urlLocale = localeFromUrl("locale"); | ||||
|     if (urlLocale !== null && urlLocale !== "") { | ||||
|         code = urlLocale; | ||||
|     } | ||||
|  | ||||
|     const locale = getBestMatchLocale(code); | ||||
|     if (!locale) { | ||||
|         console.warn(`authentik/locale: failed to find locale for code ${code}`); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     locale.locale().then(() => { | ||||
|         console.debug(`authentik/locale: Loaded locale '${code}'`); | ||||
|         if (getLocale() === code) { | ||||
|             return; | ||||
|         } | ||||
|         console.debug(`Setting Locale to ... ${locale.label()} (${locale.code})`); | ||||
|         setLocale(locale.code); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| export default activateLocale; | ||||
| @ -1,38 +0,0 @@ | ||||
| import { globalAK } from "@goauthentik/common/global"; | ||||
|  | ||||
| import { activateLocale } from "./activateLocale"; | ||||
| import { setLocale } from "./configureLocale"; | ||||
| import { DEFAULT_FALLBACK } from "./definitions"; | ||||
| import { findSupportedLocale, localeFromUrl } from "./helpers"; | ||||
|  | ||||
| const isLocaleCandidate = (v: unknown): v is string => typeof v === "string" && v !== ""; | ||||
|  | ||||
| export function autoDetectLanguage(defaultLanguage = "en") { | ||||
|     // Always load en locale at the start so we have something and don't error | ||||
|     setLocale(defaultLanguage); | ||||
|  | ||||
|     // Get all locales we can, in order | ||||
|     // - Global authentik settings (contains user settings) | ||||
|     // - URL parameter | ||||
|     // - Navigator | ||||
|     // - Fallback (en) | ||||
|  | ||||
|     const localeCandidates: string[] = [ | ||||
|         globalAK()?.locale, | ||||
|         localeFromUrl("locale"), | ||||
|         window.navigator.language, | ||||
|         DEFAULT_FALLBACK, | ||||
|     ].filter(isLocaleCandidate); | ||||
|  | ||||
|     const firstSupportedLocale = findSupportedLocale(localeCandidates); | ||||
|  | ||||
|     if (!firstSupportedLocale) { | ||||
|         console.debug(`authentik/locale: No locale for '${localeCandidates}', falling back to en`); | ||||
|         activateLocale(defaultLanguage); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     activateLocale(firstSupportedLocale.code); | ||||
| } | ||||
|  | ||||
| export default autoDetectLanguage; | ||||
| @ -1,17 +0,0 @@ | ||||
| import { configureLocalization } from "@lit/localize"; | ||||
|  | ||||
| import { sourceLocale, targetLocales } from "../../../locale-codes"; | ||||
| import { getBestMatchLocale } from "./helpers"; | ||||
|  | ||||
| export const { getLocale, setLocale } = configureLocalization({ | ||||
|     sourceLocale, | ||||
|     targetLocales, | ||||
|     loadLocale: async (locale: string) => { | ||||
|         const localeDef = getBestMatchLocale(locale); | ||||
|         if (!localeDef) { | ||||
|             console.warn(`Unrecognized locale: ${localeDef}`); | ||||
|             return Promise.reject(""); | ||||
|         } | ||||
|         return localeDef.locale(); | ||||
|     }, | ||||
| }); | ||||
| @ -1,31 +0,0 @@ | ||||
| import { LOCALES as RAW_LOCALES, enLocale } from "./definitions"; | ||||
| import { AkLocale } from "./types"; | ||||
|  | ||||
| // NOTE: This is the definition of the LOCALES table that most of the code uses. The 'definitions' | ||||
| // file is relatively pure, but here we establish that we want the English locale to loaded when an | ||||
| // application is first instantiated. | ||||
|  | ||||
| export const LOCALES = RAW_LOCALES.map((locale) => | ||||
|     locale.code === "en" ? { ...locale, locale: async () => enLocale } : locale, | ||||
| ); | ||||
|  | ||||
| // First attempt a precise match, then see if there's a precise match on the requested locale's | ||||
| // prefix, then find the *first* locale for which that locale's prefix matches the requested prefix. | ||||
|  | ||||
| export function getBestMatchLocale(locale: string): AkLocale | undefined { | ||||
|     return LOCALES.find((l) => l.match.test(locale)); | ||||
| } | ||||
|  | ||||
| // This looks weird, but it's sensible: we have several candidates, and we want to find the first | ||||
| // one that has a supported locale. Then, from *that*, we have to extract that first supported | ||||
| // locale. | ||||
|  | ||||
| export function findSupportedLocale(candidates: string[]) { | ||||
|     const candidate = candidates.find((candidate: string) => getBestMatchLocale(candidate)); | ||||
|     return candidate ? getBestMatchLocale(candidate) : undefined; | ||||
| } | ||||
|  | ||||
| export function localeFromUrl(param = "locale") { | ||||
|     const url = new URL(window.location.href); | ||||
|     return url.searchParams.get(param) || ""; | ||||
| } | ||||
| @ -1,6 +0,0 @@ | ||||
| import activateLocale from "./activateLocale"; | ||||
| import autoDetectLanguage from "./autodetectLanguage"; | ||||
| import { getLocale, setLocale } from "./configureLocale"; | ||||
| import { LOCALES } from "./helpers"; | ||||
|  | ||||
| export { LOCALES, getLocale, setLocale, activateLocale, autoDetectLanguage }; | ||||
| @ -1,5 +1,6 @@ | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { activateLocale } from "@goauthentik/common/ui/locale"; | ||||
| import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants"; | ||||
| import { customEvent } from "@goauthentik/elements/utils/customEvents"; | ||||
|  | ||||
| import { CoreApi, ResponseError, SessionUser } from "@goauthentik/api"; | ||||
|  | ||||
| @ -23,7 +24,7 @@ export function me(): Promise<SessionUser> { | ||||
|                     console.debug( | ||||
|                         `authentik/locale: Activating user's configured locale '${locale}'`, | ||||
|                     ); | ||||
|                     activateLocale(locale); | ||||
|                     window.dispatchEvent(customEvent(EVENT_LOCALE_REQUEST, { locale })); | ||||
|                 } | ||||
|                 return user; | ||||
|             }) | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| import { config, tenant } from "@goauthentik/common/api/config"; | ||||
| import { EVENT_LOCALE_CHANGE, EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; | ||||
| import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; | ||||
| import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; | ||||
| import { adaptCSS } from "@goauthentik/common/utils"; | ||||
|  | ||||
| import { localized } from "@lit/localize"; | ||||
| import { LitElement } from "lit"; | ||||
| import { state } from "lit/decorators.js"; | ||||
|  | ||||
| @ -45,6 +46,7 @@ export interface AdoptedStyleSheetsElement { | ||||
|  | ||||
| const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)"; | ||||
|  | ||||
| @localized() | ||||
| export class AKElement extends LitElement { | ||||
|     _mediaMatcher?: MediaQueryList; | ||||
|     _mediaMatcherHandler?: (ev?: MediaQueryListEvent) => void; | ||||
| @ -53,14 +55,9 @@ export class AKElement extends LitElement { | ||||
|     get activeTheme(): UiThemeEnum | undefined { | ||||
|         return this._activeTheme; | ||||
|     } | ||||
|     private _handleLocaleChange: () => void; | ||||
|  | ||||
|     constructor() { | ||||
|         super(); | ||||
|         this._handleLocaleChange = (() => { | ||||
|             this.requestUpdate(); | ||||
|         }).bind(this); | ||||
|         window.addEventListener(EVENT_LOCALE_CHANGE, this._handleLocaleChange); | ||||
|     } | ||||
|  | ||||
|     protected createRenderRoot(): ShadowRoot | Element { | ||||
| @ -162,11 +159,6 @@ export class AKElement extends LitElement { | ||||
|         this._activeTheme = theme; | ||||
|         this.requestUpdate(); | ||||
|     } | ||||
|  | ||||
|     disconnectedCallback() { | ||||
|         super.disconnectedCallback(); | ||||
|         window.removeEventListener(EVENT_LOCALE_CHANGE, this._handleLocaleChange); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export class Interface extends AKElement { | ||||
|  | ||||
| @ -0,0 +1,51 @@ | ||||
| import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants"; | ||||
| import { customEvent } from "@goauthentik/elements/utils/customEvents"; | ||||
|  | ||||
| import { localized, msg } from "@lit/localize"; | ||||
| import { LitElement, html } from "lit"; | ||||
| import { customElement } from "lit/decorators.js"; | ||||
|  | ||||
| import "./ak-locale-context"; | ||||
|  | ||||
| export default { | ||||
|     title: "Elements / Shell / Locale Context", | ||||
| }; | ||||
|  | ||||
| @localized() | ||||
| @customElement("ak-locale-demo-component") | ||||
| export class AKLocaleDemoComponent extends LitElement { | ||||
|     render() { | ||||
|         return html`<span>${msg("Everything is ok.")}</span>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @localized() | ||||
| @customElement("ak-locale-sensitive-demo-component") | ||||
| export class AKLocaleSensitiveDemoComponent extends LitElement { | ||||
|     render() { | ||||
|         return html`<p>${msg("Everything is ok.")}</p>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export const InFrench = () => | ||||
|     html`<div style="background: #fff; padding: 4em"> | ||||
|         <ak-locale-context locale="fr_FR" | ||||
|             ><ak-locale-demo-component | ||||
|                 >Everything is not ok.</ak-locale-demo-component | ||||
|             ></ak-locale-context | ||||
|         > | ||||
|     </div>`; | ||||
|  | ||||
| export const SwitchingBackAndForth = () => { | ||||
|     let lang = "en"; | ||||
|     window.setInterval(() => { | ||||
|         lang = lang === "en" ? "fr_FR" : "en"; | ||||
|         window.dispatchEvent(customEvent(EVENT_LOCALE_REQUEST, { locale: lang })); | ||||
|     }, 1000); | ||||
|  | ||||
|     return html`<div style="background: #fff; padding: 4em"> | ||||
|         <ak-locale-context locale="fr_FR"> | ||||
|             <ak-locale-sensitive-demo-component></ak-locale-sensitive-demo-component | ||||
|         ></ak-locale-context> | ||||
|     </div>`; | ||||
| }; | ||||
							
								
								
									
										115
									
								
								web/src/elements/ak-locale-context/ak-locale-context.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								web/src/elements/ak-locale-context/ak-locale-context.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| import { EVENT_LOCALE_CHANGE } from "@goauthentik/common/constants"; | ||||
| import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants"; | ||||
| import { customEvent, isCustomEvent } from "@goauthentik/elements/utils/customEvents"; | ||||
|  | ||||
| import { provide } from "@lit-labs/context"; | ||||
| import { LitElement, html } from "lit"; | ||||
| import { customElement, property } from "lit/decorators.js"; | ||||
|  | ||||
| import { initializeLocalization } from "./configureLocale"; | ||||
| import type { LocaleGetter, LocaleSetter } from "./configureLocale"; | ||||
| import locale from "./context"; | ||||
| import { | ||||
|     DEFAULT_LOCALE, | ||||
|     autoDetectLanguage, | ||||
|     getBestMatchLocale, | ||||
|     localeCodeFromUrl, | ||||
| } from "./helpers"; | ||||
|  | ||||
| /** | ||||
|  * A component to manage your locale settings. | ||||
|  * | ||||
|  * ## Details | ||||
|  * | ||||
|  * This component exists to take a locale setting from several different places, find the | ||||
|  * appropriate locale file in our catalog of locales, and set the lit-localization context | ||||
|  * appropriately. If that works, it sends off an event saying so. | ||||
|  * | ||||
|  * @element ak-locale-context | ||||
|  * @slot - The content which consumes this context | ||||
|  * @fires ak-locale-change - When a valid locale has been swapped in | ||||
|  */ | ||||
| @customElement("ak-locale-context") | ||||
| export class LocaleContext extends LitElement { | ||||
|     /// @attribute The text representation of the current locale */ | ||||
|     @provide({ context: locale }) | ||||
|     @property({ attribute: true, type: String }) | ||||
|     locale = DEFAULT_LOCALE; | ||||
|  | ||||
|     /// @attribute The URL parameter to look for (if any) | ||||
|     @property({ attribute: true, type: String }) | ||||
|     param = "locale"; | ||||
|  | ||||
|     getLocale: LocaleGetter; | ||||
|  | ||||
|     setLocale: LocaleSetter; | ||||
|  | ||||
|     constructor(code = DEFAULT_LOCALE) { | ||||
|         super(); | ||||
|         this.notifyApplication = this.notifyApplication.bind(this); | ||||
|         this.updateLocaleHandler = this.updateLocaleHandler.bind(this); | ||||
|         try { | ||||
|             const [getLocale, setLocale] = initializeLocalization(); | ||||
|             this.getLocale = getLocale; | ||||
|             this.setLocale = setLocale; | ||||
|             this.setLocale(code).then(() => { | ||||
|                 window.setTimeout(this.notifyApplication, 0); | ||||
|             }); | ||||
|         } catch (e) { | ||||
|             throw new Error(`Developer error: Must have only one locale context per session: ${e}`); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     connectedCallback() { | ||||
|         super.connectedCallback(); | ||||
|         const localeRequest = autoDetectLanguage(this.locale); | ||||
|         this.updateLocale(localeRequest); | ||||
|         window.addEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler); | ||||
|     } | ||||
|  | ||||
|     disconnectedCallback() { | ||||
|         window.removeEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler); | ||||
|         super.disconnectedCallback(); | ||||
|     } | ||||
|  | ||||
|     updateLocaleHandler(ev: Event) { | ||||
|         if (!isCustomEvent(ev)) { | ||||
|             console.warn(`Received a non-custom event at EVENT_LOCALE_REQUEST: ${ev}`); | ||||
|             return; | ||||
|         } | ||||
|         console.log("Locale update request received."); | ||||
|         this.updateLocale(ev.detail.locale); | ||||
|     } | ||||
|  | ||||
|     updateLocale(code: string) { | ||||
|         const urlCode = localeCodeFromUrl(this.param); | ||||
|         const requestedLocale = urlCode ? urlCode : code; | ||||
|         const locale = getBestMatchLocale(requestedLocale); | ||||
|         if (!locale) { | ||||
|             console.warn(`authentik/locale: failed to find locale for code ${code}`); | ||||
|             return; | ||||
|         } | ||||
|         locale.locale().then(() => { | ||||
|             console.debug(`authentik/locale: Loaded locale '${code}'`); | ||||
|             if (this.getLocale() === code) { | ||||
|                 return; | ||||
|             } | ||||
|             console.debug(`Setting Locale to ... ${locale.label()} (${locale.code})`); | ||||
|             this.setLocale(locale.code).then(() => { | ||||
|                 window.setTimeout(this.notifyApplication, 0); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     notifyApplication() { | ||||
|         // You will almost never have cause to catch this event. Lit's own `@localized()` decorator | ||||
|         // works just fine for almost every use case. | ||||
|         this.dispatchEvent(customEvent(EVENT_LOCALE_CHANGE)); | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         return html`<slot></slot>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default LocaleContext; | ||||
							
								
								
									
										40
									
								
								web/src/elements/ak-locale-context/configureLocale.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								web/src/elements/ak-locale-context/configureLocale.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| import { sourceLocale, targetLocales } from "@goauthentik/app/locale-codes"; | ||||
|  | ||||
| import { configureLocalization } from "@lit/localize"; | ||||
|  | ||||
| import { getBestMatchLocale } from "./helpers"; | ||||
|  | ||||
| type LocaleGetter = ReturnType<typeof configureLocalization>["getLocale"]; | ||||
| type LocaleSetter = ReturnType<typeof configureLocalization>["setLocale"]; | ||||
|  | ||||
| // Internal use only. | ||||
| // | ||||
| // This is where the lit-localization module is initialized with our loader, which associates our | ||||
| // collection of locales with its getter and setter functions. | ||||
|  | ||||
| let getLocale: LocaleGetter | undefined = undefined; | ||||
| let setLocale: LocaleSetter | undefined = undefined; | ||||
|  | ||||
| export function initializeLocalization(): [LocaleGetter, LocaleSetter] { | ||||
|     if (getLocale && setLocale) { | ||||
|         return [getLocale, setLocale]; | ||||
|     } | ||||
|  | ||||
|     ({ getLocale, setLocale } = configureLocalization({ | ||||
|         sourceLocale, | ||||
|         targetLocales, | ||||
|         loadLocale: async (locale: string) => { | ||||
|             const localeDef = getBestMatchLocale(locale); | ||||
|             if (!localeDef) { | ||||
|                 console.warn(`Unrecognized locale: ${localeDef}`); | ||||
|                 return Promise.reject(""); | ||||
|             } | ||||
|             return localeDef.locale(); | ||||
|         }, | ||||
|     })); | ||||
|  | ||||
|     return [getLocale, setLocale]; | ||||
| } | ||||
|  | ||||
| export default initializeLocalization; | ||||
| export type { LocaleGetter, LocaleSetter }; | ||||
							
								
								
									
										4
									
								
								web/src/elements/ak-locale-context/context.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								web/src/elements/ak-locale-context/context.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| import { createContext } from "@lit-labs/context"; | ||||
|  | ||||
| export const localeContext = createContext<string>("locale"); | ||||
| export default localeContext; | ||||
							
								
								
									
										69
									
								
								web/src/elements/ak-locale-context/helpers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								web/src/elements/ak-locale-context/helpers.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| import { globalAK } from "@goauthentik/common/global"; | ||||
|  | ||||
| import { LOCALES as RAW_LOCALES, enLocale } from "./definitions"; | ||||
| import { AkLocale } from "./types"; | ||||
|  | ||||
| export const DEFAULT_LOCALE = "en"; | ||||
|  | ||||
| export const EVENT_REQUEST_LOCALE = "ak-request-locale"; | ||||
|  | ||||
| const TOMBSTONE = "⛼⛼tombstone⛼⛼"; | ||||
|  | ||||
| // NOTE: This is the definition of the LOCALES table that most of the code uses. The 'definitions' | ||||
| // file is relatively pure, but here we establish that we want the English locale to loaded when an | ||||
| // application is first instantiated. | ||||
|  | ||||
| export const LOCALES = RAW_LOCALES.map((locale) => | ||||
|     locale.code === "en" ? { ...locale, locale: async () => enLocale } : locale, | ||||
| ); | ||||
|  | ||||
| export function getBestMatchLocale(locale: string): AkLocale | undefined { | ||||
|     return LOCALES.find((l) => l.match.test(locale)); | ||||
| } | ||||
|  | ||||
| // This looks weird, but it's sensible: we have several candidates, and we want to find the first | ||||
| // one that has a supported locale. Then, from *that*, we have to extract that first supported | ||||
| // locale. | ||||
|  | ||||
| export function findSupportedLocale(candidates: string[]) { | ||||
|     const candidate = candidates.find((candidate: string) => getBestMatchLocale(candidate)); | ||||
|     return candidate ? getBestMatchLocale(candidate) : undefined; | ||||
| } | ||||
|  | ||||
| export function localeCodeFromUrl(param = "locale") { | ||||
|     const url = new URL(window.location.href); | ||||
|     return url.searchParams.get(param) || ""; | ||||
| } | ||||
|  | ||||
| // Get all locales we can, in order | ||||
| // - Global authentik settings (contains user settings) | ||||
| // - URL parameter | ||||
| // - A requested code passed in, if any | ||||
| // - Navigator | ||||
| // - Fallback (en) | ||||
|  | ||||
| const isLocaleCandidate = (v: unknown): v is string => | ||||
|     typeof v === "string" && v !== "" && v !== TOMBSTONE; | ||||
|  | ||||
| export function autoDetectLanguage(requestedCode?: string): string { | ||||
|     const localeCandidates: string[] = [ | ||||
|         globalAK()?.locale ?? TOMBSTONE, | ||||
|         localeCodeFromUrl("locale"), | ||||
|         requestedCode ?? TOMBSTONE, | ||||
|         window.navigator?.language ?? TOMBSTONE, | ||||
|         DEFAULT_LOCALE, | ||||
|     ].filter(isLocaleCandidate); | ||||
|  | ||||
|     const firstSupportedLocale = findSupportedLocale(localeCandidates); | ||||
|  | ||||
|     if (!firstSupportedLocale) { | ||||
|         console.debug( | ||||
|             `authentik/locale: No locale found for '[${localeCandidates}.join(',')]', falling back to ${DEFAULT_LOCALE}`, | ||||
|         ); | ||||
|         return DEFAULT_LOCALE; | ||||
|     } | ||||
|  | ||||
|     return firstSupportedLocale.code; | ||||
| } | ||||
|  | ||||
| export default autoDetectLanguage; | ||||
							
								
								
									
										4
									
								
								web/src/elements/ak-locale-context/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								web/src/elements/ak-locale-context/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| import LocaleContext from "./ak-locale-context"; | ||||
|  | ||||
| export { LocaleContext }; | ||||
| export default LocaleContext; | ||||
							
								
								
									
										10
									
								
								web/src/elements/ak-locale-context/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								web/src/elements/ak-locale-context/types.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| import type { LocaleModule } from "@lit/localize"; | ||||
|  | ||||
| export type LocaleRow = [string, RegExp, () => string, () => Promise<LocaleModule>]; | ||||
|  | ||||
| export type AkLocale = { | ||||
|     code: string; | ||||
|     match: RegExp; | ||||
|     label: () => string; | ||||
|     locale: () => Promise<LocaleModule>; | ||||
| }; | ||||
							
								
								
									
										13
									
								
								web/src/elements/utils/customEvents.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/src/elements/utils/customEvents.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| export const customEvent = (name: string, details = {}) => | ||||
|     new CustomEvent(name as string, { | ||||
|         composed: true, | ||||
|         bubbles: true, | ||||
|         detail: details, | ||||
|     }); | ||||
|  | ||||
| // "Unknown" seems to violate some obscure Typescript rule and doesn't work here, although it | ||||
| // should. | ||||
| // | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||
| export const isCustomEvent = (v: any): v is CustomEvent => | ||||
|     v instanceof CustomEvent && "detail" in v; | ||||
| @ -10,6 +10,7 @@ import { first } from "@goauthentik/common/utils"; | ||||
| import { WebsocketClient } from "@goauthentik/common/ws"; | ||||
| import { Interface } from "@goauthentik/elements/Base"; | ||||
| import "@goauthentik/elements/LoadingOverlay"; | ||||
| import "@goauthentik/elements/ak-locale-context"; | ||||
| import "@goauthentik/flow/stages/FlowErrorStage"; | ||||
| import "@goauthentik/flow/stages/RedirectStage"; | ||||
| import { StageHost } from "@goauthentik/flow/stages/base"; | ||||
| @ -487,7 +488,8 @@ export class FlowExecutor extends Interface implements StageHost { | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<div class="pf-c-background-image">${this.renderBackgroundOverlay()}</div> | ||||
|         return html` <ak-locale-context> | ||||
|             <div class="pf-c-background-image">${this.renderBackgroundOverlay()}</div> | ||||
|             <div class="pf-c-page__drawer"> | ||||
|                 <div class="pf-c-drawer ${this.inspectorOpen ? "pf-m-expanded" : "pf-m-collapsed"}"> | ||||
|                     <div class="pf-c-drawer__main"> | ||||
| @ -541,6 +543,7 @@ export class FlowExecutor extends Interface implements StageHost { | ||||
|                         ${until(this.renderInspector())} | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div>`; | ||||
|             </div> | ||||
|         </ak-locale-context>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; | ||||
| import "@goauthentik/elements/messages/MessageContainer"; | ||||
| import "@goauthentik/flow/FlowExecutor"; | ||||
| // Statically import some stages to speed up load speed | ||||
| @ -14,5 +13,3 @@ import "@goauthentik/flow/stages/identification/IdentificationStage"; | ||||
| import "@goauthentik/flow/stages/password/PasswordStage"; | ||||
|  | ||||
| // end of stage import | ||||
|  | ||||
| autoDetectLanguage(); | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { LOCALES } from "@goauthentik/common/ui/locale"; | ||||
| import { rootInterface } from "@goauthentik/elements/Base"; | ||||
| import "@goauthentik/elements/Divider"; | ||||
| import "@goauthentik/elements/EmptyState"; | ||||
| import { LOCALES } from "@goauthentik/elements/ak-locale-context/definitions"; | ||||
| import "@goauthentik/elements/forms/FormElement"; | ||||
| import { BaseStage } from "@goauthentik/flow/stages/base"; | ||||
|  | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| import { CSRFHeaderName } from "@goauthentik/common/api/middleware"; | ||||
| import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; | ||||
| import { globalAK } from "@goauthentik/common/global"; | ||||
| import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; | ||||
| import { first, getCookie } from "@goauthentik/common/utils"; | ||||
| import { Interface } from "@goauthentik/elements/Base"; | ||||
| import "@goauthentik/elements/ak-locale-context"; | ||||
| import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand"; | ||||
| import "rapidoc"; | ||||
|  | ||||
| @ -13,8 +13,6 @@ import { ifDefined } from "lit/directives/if-defined.js"; | ||||
|  | ||||
| import { UiThemeEnum } from "@goauthentik/api"; | ||||
|  | ||||
| autoDetectLanguage(); | ||||
|  | ||||
| @customElement("ak-api-browser") | ||||
| export class APIBrowser extends Interface { | ||||
|     @property() | ||||
| @ -66,45 +64,50 @@ export class APIBrowser extends Interface { | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html` | ||||
|             <rapi-doc | ||||
|                 spec-url=${ifDefined(this.schemaPath)} | ||||
|                 heading-text="" | ||||
|                 theme="light" | ||||
|                 render-style="read" | ||||
|                 default-schema-tab="schema" | ||||
|                 primary-color="#fd4b2d" | ||||
|                 nav-bg-color="#212427" | ||||
|                 bg-color=${this.bgColor} | ||||
|                 text-color=${this.textColor} | ||||
|                 nav-text-color="#ffffff" | ||||
|                 nav-hover-bg-color="#3c3f42" | ||||
|                 nav-accent-color="#4f5255" | ||||
|                 nav-hover-text-color="#ffffff" | ||||
|                 use-path-in-nav-bar="true" | ||||
|                 nav-item-spacing="relaxed" | ||||
|                 allow-server-selection="false" | ||||
|                 show-header="false" | ||||
|                 allow-spec-url-load="false" | ||||
|                 allow-spec-file-load="false" | ||||
|                 show-method-in-nav-bar="as-colored-text" | ||||
|                 @before-try=${( | ||||
|                     e: CustomEvent<{ | ||||
|                         request: { | ||||
|                             headers: Headers; | ||||
|                         }; | ||||
|                     }>, | ||||
|                 ) => { | ||||
|                     e.detail.request.headers.append(CSRFHeaderName, getCookie("authentik_csrf")); | ||||
|                 }} | ||||
|             > | ||||
|                 <div slot="nav-logo"> | ||||
|                     <img | ||||
|                         alt="authentik Logo" | ||||
|                         class="logo" | ||||
|                         src="${first(this.tenant?.brandingLogo, DefaultTenant.brandingLogo)}" | ||||
|                     /> | ||||
|                 </div> | ||||
|             </rapi-doc> | ||||
|             <ak-locale-context> | ||||
|                 <rapi-doc | ||||
|                     spec-url=${ifDefined(this.schemaPath)} | ||||
|                     heading-text="" | ||||
|                     theme="light" | ||||
|                     render-style="read" | ||||
|                     default-schema-tab="schema" | ||||
|                     primary-color="#fd4b2d" | ||||
|                     nav-bg-color="#212427" | ||||
|                     bg-color=${this.bgColor} | ||||
|                     text-color=${this.textColor} | ||||
|                     nav-text-color="#ffffff" | ||||
|                     nav-hover-bg-color="#3c3f42" | ||||
|                     nav-accent-color="#4f5255" | ||||
|                     nav-hover-text-color="#ffffff" | ||||
|                     use-path-in-nav-bar="true" | ||||
|                     nav-item-spacing="relaxed" | ||||
|                     allow-server-selection="false" | ||||
|                     show-header="false" | ||||
|                     allow-spec-url-load="false" | ||||
|                     allow-spec-file-load="false" | ||||
|                     show-method-in-nav-bar="as-colored-text" | ||||
|                     @before-try=${( | ||||
|                         e: CustomEvent<{ | ||||
|                             request: { | ||||
|                                 headers: Headers; | ||||
|                             }; | ||||
|                         }>, | ||||
|                     ) => { | ||||
|                         e.detail.request.headers.append( | ||||
|                             CSRFHeaderName, | ||||
|                             getCookie("authentik_csrf"), | ||||
|                         ); | ||||
|                     }} | ||||
|                 > | ||||
|                     <div slot="nav-logo"> | ||||
|                         <img | ||||
|                             alt="authentik Logo" | ||||
|                             class="logo" | ||||
|                             src="${first(this.tenant?.brandingLogo, DefaultTenant.brandingLogo)}" | ||||
|                         /> | ||||
|                     </div> | ||||
|                 </rapi-doc> | ||||
|             </ak-locale-context> | ||||
|         `; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| import { globalAK } from "@goauthentik/common/global"; | ||||
| import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; | ||||
| import { Interface } from "@goauthentik/elements/Base"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| @ -13,8 +12,6 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
|  | ||||
| import { UiThemeEnum } from "@goauthentik/api"; | ||||
|  | ||||
| autoDetectLanguage(); | ||||
|  | ||||
| @customElement("ak-loading") | ||||
| export class Loading extends Interface { | ||||
|     static get styles(): CSSResult[] { | ||||
|  | ||||
| @ -6,12 +6,11 @@ import { | ||||
| } from "@goauthentik/common/constants"; | ||||
| import { configureSentry } from "@goauthentik/common/sentry"; | ||||
| import { UserDisplay } from "@goauthentik/common/ui/config"; | ||||
| import { autoDetectLanguage } from "@goauthentik/common/ui/locale"; | ||||
| import { me } from "@goauthentik/common/users"; | ||||
| import { first } from "@goauthentik/common/utils"; | ||||
| import { WebsocketClient } from "@goauthentik/common/ws"; | ||||
| import { Interface } from "@goauthentik/elements/Base"; | ||||
| import "@goauthentik/elements/buttons/ActionButton"; | ||||
| import "@goauthentik/elements/ak-locale-context"; | ||||
| import "@goauthentik/elements/messages/MessageContainer"; | ||||
| import "@goauthentik/elements/notifications/APIDrawer"; | ||||
| import "@goauthentik/elements/notifications/NotificationDrawer"; | ||||
| @ -36,9 +35,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
| import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; | ||||
|  | ||||
| import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api"; | ||||
|  | ||||
| autoDetectLanguage(); | ||||
| import { EventsApi, SessionUser } from "@goauthentik/api"; | ||||
|  | ||||
| @customElement("ak-interface-user") | ||||
| export class UserInterface extends Interface { | ||||
| @ -150,157 +147,168 @@ export class UserInterface extends Interface { | ||||
|             default: | ||||
|                 userDisplay = this.me.user.username; | ||||
|         } | ||||
|         return html`<div class="pf-c-page"> | ||||
|             <div class="background-wrapper" style="${this.uiConfig.theme.background}"></div> | ||||
|             <header class="pf-c-page__header"> | ||||
|                 <div class="pf-c-page__header-brand"> | ||||
|                     <a href="#/" class="pf-c-page__header-brand-link"> | ||||
|                         <img | ||||
|                             class="pf-c-brand" | ||||
|                             src="${first(this.tenant?.brandingLogo, DefaultTenant.brandingLogo)}" | ||||
|                             alt="${(this.tenant?.brandingTitle, DefaultTenant.brandingTitle)}" | ||||
|                         /> | ||||
|                     </a> | ||||
|                 </div> | ||||
|                 <div class="pf-c-page__header-tools"> | ||||
|                     <div class="pf-c-page__header-tools-group"> | ||||
|                         ${this.uiConfig.enabledFeatures.apiDrawer | ||||
|                             ? html`<div | ||||
|                                   class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg" | ||||
|                               > | ||||
|                                   <button | ||||
|                                       class="pf-c-button pf-m-plain" | ||||
|                                       type="button" | ||||
|                                       @click=${() => { | ||||
|                                           this.apiDrawerOpen = !this.apiDrawerOpen; | ||||
|                                           updateURLParams({ | ||||
|                                               apiDrawerOpen: this.apiDrawerOpen, | ||||
|                                           }); | ||||
|                                       }} | ||||
|                                   > | ||||
|                                       <i class="fas fa-code" aria-hidden="true"></i> | ||||
|                                   </button> | ||||
|                               </div>` | ||||
|                             : html``} | ||||
|                         ${this.uiConfig.enabledFeatures.notificationDrawer | ||||
|                             ? html`<div | ||||
|                                   class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg" | ||||
|                               > | ||||
|                                   <button | ||||
|                                       class="pf-c-button pf-m-plain" | ||||
|                                       type="button" | ||||
|                                       aria-label="${msg("Unread notifications")}" | ||||
|                                       @click=${() => { | ||||
|                                           this.notificationDrawerOpen = | ||||
|                                               !this.notificationDrawerOpen; | ||||
|                                           updateURLParams({ | ||||
|                                               notificationDrawerOpen: this.notificationDrawerOpen, | ||||
|                                           }); | ||||
|                                       }} | ||||
|                                   > | ||||
|                                       <span | ||||
|                                           class="pf-c-notification-badge ${this.notificationsCount > | ||||
|                                           0 | ||||
|                                               ? "pf-m-unread" | ||||
|                                               : ""}" | ||||
|                                       > | ||||
|                                           <i class="pf-icon-bell" aria-hidden="true"></i> | ||||
|                                           <span class="pf-c-notification-badge__count" | ||||
|                                               >${this.notificationsCount}</span | ||||
|                                           > | ||||
|                                       </span> | ||||
|                                   </button> | ||||
|                               </div> ` | ||||
|                             : html``} | ||||
|                         ${this.uiConfig.enabledFeatures.settings | ||||
|                             ? html` <div class="pf-c-page__header-tools-item"> | ||||
|                                   <a class="pf-c-button pf-m-plain" type="button" href="#/settings"> | ||||
|                                       <i class="fas fa-cog" aria-hidden="true"></i> | ||||
|                                   </a> | ||||
|                               </div>` | ||||
|                             : html``} | ||||
|                         <div class="pf-c-page__header-tools-item"> | ||||
|                             <a href="/flows/-/default/invalidation/" class="pf-c-button pf-m-plain"> | ||||
|                                 <i class="fas fa-sign-out-alt" aria-hidden="true"></i> | ||||
|                             </a> | ||||
|                         </div> | ||||
|                         ${this.me.user.isSuperuser | ||||
|                             ? html`<a | ||||
|                                   class="pf-c-button pf-m-primary pf-m-small pf-u-display-none pf-u-display-block-on-md" | ||||
|                                   href="/if/admin" | ||||
|                               > | ||||
|                                   ${msg("Admin interface")} | ||||
|                               </a>` | ||||
|                             : html``} | ||||
|         return html` <ak-locale-context> | ||||
|             <div class="pf-c-page"> | ||||
|                 <div class="background-wrapper" style="${this.uiConfig.theme.background}"></div> | ||||
|                 <header class="pf-c-page__header"> | ||||
|                     <div class="pf-c-page__header-brand"> | ||||
|                         <a href="#/" class="pf-c-page__header-brand-link"> | ||||
|                             <img | ||||
|                                 class="pf-c-brand" | ||||
|                                 src="${first( | ||||
|                                     this.tenant?.brandingLogo, | ||||
|                                     DefaultTenant.brandingLogo, | ||||
|                                 )}" | ||||
|                                 alt="${(this.tenant?.brandingTitle, DefaultTenant.brandingTitle)}" | ||||
|                             /> | ||||
|                         </a> | ||||
|                     </div> | ||||
|                     ${this.me.original | ||||
|                         ? html`  | ||||
|                               <div class="pf-c-page__header-tools"> | ||||
|                                   <div class="pf-c-page__header-tools-group"> | ||||
|                                       <ak-action-button | ||||
|                                           class="pf-m-warning pf-m-small" | ||||
|                                           .apiRequest=${() => { | ||||
|                                               return new CoreApi(DEFAULT_CONFIG) | ||||
|                                                   .coreUsersImpersonateEndRetrieve() | ||||
|                                                   .then(() => { | ||||
|                                                       window.location.reload(); | ||||
|                                                   }); | ||||
|                     <div class="pf-c-page__header-tools"> | ||||
|                         <div class="pf-c-page__header-tools-group"> | ||||
|                             ${this.uiConfig.enabledFeatures.apiDrawer | ||||
|                                 ? html`<div | ||||
|                                       class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg" | ||||
|                                   > | ||||
|                                       <button | ||||
|                                           class="pf-c-button pf-m-plain" | ||||
|                                           type="button" | ||||
|                                           @click=${() => { | ||||
|                                               this.apiDrawerOpen = !this.apiDrawerOpen; | ||||
|                                               updateURLParams({ | ||||
|                                                   apiDrawerOpen: this.apiDrawerOpen, | ||||
|                                               }); | ||||
|                                           }} | ||||
|                                       > | ||||
|                                           <i class="fas fa-code" aria-hidden="true"></i> | ||||
|                                       </button> | ||||
|                                   </div>` | ||||
|                                 : html``} | ||||
|                             ${this.uiConfig.enabledFeatures.notificationDrawer | ||||
|                                 ? html`<div | ||||
|                                       class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg" | ||||
|                                   > | ||||
|                                       <button | ||||
|                                           class="pf-c-button pf-m-plain" | ||||
|                                           type="button" | ||||
|                                           aria-label="${msg("Unread notifications")}" | ||||
|                                           @click=${() => { | ||||
|                                               this.notificationDrawerOpen = | ||||
|                                                   !this.notificationDrawerOpen; | ||||
|                                               updateURLParams({ | ||||
|                                                   notificationDrawerOpen: | ||||
|                                                       this.notificationDrawerOpen, | ||||
|                                               }); | ||||
|                                           }} | ||||
|                                       > | ||||
|                                           <span | ||||
|                                               class="pf-c-notification-badge ${this | ||||
|                                                   .notificationsCount > 0 | ||||
|                                                   ? "pf-m-unread" | ||||
|                                                   : ""}" | ||||
|                                           > | ||||
|                                               <i class="pf-icon-bell" aria-hidden="true"></i> | ||||
|                                               <span class="pf-c-notification-badge__count" | ||||
|                                                   >${this.notificationsCount}</span | ||||
|                                               > | ||||
|                                           </span> | ||||
|                                       </button> | ||||
|                                   </div> ` | ||||
|                                 : html``} | ||||
|                             ${this.uiConfig.enabledFeatures.settings | ||||
|                                 ? html` <div class="pf-c-page__header-tools-item"> | ||||
|                                       <a | ||||
|                                           class="pf-c-button pf-m-plain" | ||||
|                                           type="button" | ||||
|                                           href="#/settings" | ||||
|                                       > | ||||
|                                           <i class="fas fa-cog" aria-hidden="true"></i> | ||||
|                                       </a> | ||||
|                                   </div>` | ||||
|                                 : html``} | ||||
|                             <div class="pf-c-page__header-tools-item"> | ||||
|                                 <a | ||||
|                                     href="/flows/-/default/invalidation/" | ||||
|                                     class="pf-c-button pf-m-plain" | ||||
|                                 > | ||||
|                                     <i class="fas fa-sign-out-alt" aria-hidden="true"></i> | ||||
|                                 </a> | ||||
|                             </div> | ||||
|                             ${this.me.user.isSuperuser | ||||
|                                 ? html`<a | ||||
|                                       class="pf-c-button pf-m-primary pf-m-small pf-u-display-none pf-u-display-block-on-md" | ||||
|                                       href="/if/admin" | ||||
|                                   > | ||||
|                                       ${msg("Admin interface")} | ||||
|                                   </a>` | ||||
|                                 : html``} | ||||
|                         </div> | ||||
|                         ${this.me.original | ||||
|                             ? html`<div class="pf-c-page__header-tools"> | ||||
|                                   <div class="pf-c-page__header-tools-group"> | ||||
|                                       <a | ||||
|                                           class="pf-c-button pf-m-warning pf-m-small" | ||||
|                                           href=${`/-/impersonation/end/?back=${encodeURIComponent( | ||||
|                                               `${window.location.pathname}#${window.location.hash}`, | ||||
|                                           )}`} | ||||
|                                       > | ||||
|                                           ${msg("Stop impersonation")} | ||||
|                                       </ak-action-button> | ||||
|                                       </a> | ||||
|                                   </div> | ||||
|                               </div>` | ||||
|                         : html``} | ||||
|                     <div class="pf-c-page__header-tools-group"> | ||||
|                         <div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md"> | ||||
|                             ${userDisplay} | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <img | ||||
|                         class="pf-c-avatar" | ||||
|                         src=${this.me.user.avatar} | ||||
|                         alt="${msg("Avatar image")}" | ||||
|                     /> | ||||
|                 </div> | ||||
|             </header> | ||||
|             <div class="pf-c-page__drawer"> | ||||
|                 <div | ||||
|                     class="pf-c-drawer ${this.notificationDrawerOpen || this.apiDrawerOpen | ||||
|                         ? "pf-m-expanded" | ||||
|                         : "pf-m-collapsed"}" | ||||
|                 > | ||||
|                     <div class="pf-c-drawer__main"> | ||||
|                         <div class="pf-c-drawer__content"> | ||||
|                             <div class="pf-c-drawer__body"> | ||||
|                                 <main class="pf-c-page__main"> | ||||
|                                     <ak-router-outlet | ||||
|                                         role="main" | ||||
|                                         class="pf-l-bullseye__item pf-c-page__main" | ||||
|                                         tabindex="-1" | ||||
|                                         id="main-content" | ||||
|                                         defaultUrl="/library" | ||||
|                                         .routes=${ROUTES} | ||||
|                                     > | ||||
|                                     </ak-router-outlet> | ||||
|                                 </main> | ||||
|                             : html``} | ||||
|                         <div class="pf-c-page__header-tools-group"> | ||||
|                             <div | ||||
|                                 class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md" | ||||
|                             > | ||||
|                                 ${userDisplay} | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <ak-notification-drawer | ||||
|                             class="pf-c-drawer__panel pf-m-width-33 ${this.notificationDrawerOpen | ||||
|                                 ? "" | ||||
|                                 : "display-none"}" | ||||
|                             ?hidden=${!this.notificationDrawerOpen} | ||||
|                         ></ak-notification-drawer> | ||||
|                         <ak-api-drawer | ||||
|                             class="pf-c-drawer__panel pf-m-width-33 ${this.apiDrawerOpen | ||||
|                                 ? "" | ||||
|                                 : "display-none"}" | ||||
|                             ?hidden=${!this.apiDrawerOpen} | ||||
|                         ></ak-api-drawer> | ||||
|                         <img | ||||
|                             class="pf-c-avatar" | ||||
|                             src=${this.me.user.avatar} | ||||
|                             alt="${msg("Avatar image")}" | ||||
|                         /> | ||||
|                     </div> | ||||
|                 </header> | ||||
|                 <div class="pf-c-page__drawer"> | ||||
|                     <div | ||||
|                         class="pf-c-drawer ${this.notificationDrawerOpen || this.apiDrawerOpen | ||||
|                             ? "pf-m-expanded" | ||||
|                             : "pf-m-collapsed"}" | ||||
|                     > | ||||
|                         <div class="pf-c-drawer__main"> | ||||
|                             <div class="pf-c-drawer__content"> | ||||
|                                 <div class="pf-c-drawer__body"> | ||||
|                                     <main class="pf-c-page__main"> | ||||
|                                         <ak-router-outlet | ||||
|                                             role="main" | ||||
|                                             class="pf-l-bullseye__item pf-c-page__main" | ||||
|                                             tabindex="-1" | ||||
|                                             id="main-content" | ||||
|                                             defaultUrl="/library" | ||||
|                                             .routes=${ROUTES} | ||||
|                                         > | ||||
|                                         </ak-router-outlet> | ||||
|                                     </main> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <ak-notification-drawer | ||||
|                                 class="pf-c-drawer__panel pf-m-width-33 ${this | ||||
|                                     .notificationDrawerOpen | ||||
|                                     ? "" | ||||
|                                     : "display-none"}" | ||||
|                                 ?hidden=${!this.notificationDrawerOpen} | ||||
|                             ></ak-notification-drawer> | ||||
|                             <ak-api-drawer | ||||
|                                 class="pf-c-drawer__panel pf-m-width-33 ${this.apiDrawerOpen | ||||
|                                     ? "" | ||||
|                                     : "display-none"}" | ||||
|                                 ?hidden=${!this.apiDrawerOpen} | ||||
|                             ></ak-api-drawer> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div>`; | ||||
|         </ak-locale-context>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
|     "compilerOptions": { | ||||
|         "strict": true, | ||||
|         "paths": { | ||||
|             "@goauthentik/app/*": ["src/*"], | ||||
|             "@goauthentik/admin/*": ["src/admin/*"], | ||||
|             "@goauthentik/common/*": ["src/common/*"], | ||||
|             "@goauthentik/docs/*": ["../website/docs/*"], | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Ken Sternberg
					Ken Sternberg