diff --git a/web/package-lock.json b/web/package-lock.json index ce503219f..8be577908 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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" diff --git a/web/package.json b/web/package.json index c977418d4..0715a816e 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/src/admin/AdminInterface.ts b/web/src/admin/AdminInterface.ts index d467ad08a..d41af2692 100644 --- a/web/src/admin/AdminInterface.ts +++ b/web/src/admin/AdminInterface.ts @@ -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`
- - ${this.renderSidebarItems()} - -
-
+ -
-
-
-
- - -
+ ${this.renderSidebarItems()} + +
+
+
+
+
+
+ + +
+
+ +
- -
-
-
-
`; +
`; } renderSidebarItems(): TemplateResult { diff --git a/web/src/common/api/config.ts b/web/src/common/api/config.ts index 55a42c154..4e120a38e 100644 --- a/web/src/common/api/config.ts +++ b/web/src/common/api/config.ts @@ -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 | undefined = Promise.resolve(globalAK().tenant); diff --git a/web/src/common/constants.ts b/web/src/common/constants.ts index a0c4be949..20089142b 100644 --- a/web/src/common/constants.ts +++ b/web/src/common/constants.ts @@ -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"; diff --git a/web/src/common/ui/locale/activateLocale.ts b/web/src/common/ui/locale/activateLocale.ts deleted file mode 100644 index 5a9af5774..000000000 --- a/web/src/common/ui/locale/activateLocale.ts +++ /dev/null @@ -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; diff --git a/web/src/common/ui/locale/autodetectLanguage.ts b/web/src/common/ui/locale/autodetectLanguage.ts deleted file mode 100644 index 302c7470d..000000000 --- a/web/src/common/ui/locale/autodetectLanguage.ts +++ /dev/null @@ -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; diff --git a/web/src/common/ui/locale/configureLocale.ts b/web/src/common/ui/locale/configureLocale.ts deleted file mode 100644 index cd2e86e98..000000000 --- a/web/src/common/ui/locale/configureLocale.ts +++ /dev/null @@ -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(); - }, -}); diff --git a/web/src/common/ui/locale/helpers.ts b/web/src/common/ui/locale/helpers.ts deleted file mode 100644 index 89fd22cf3..000000000 --- a/web/src/common/ui/locale/helpers.ts +++ /dev/null @@ -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) || ""; -} diff --git a/web/src/common/ui/locale/index.ts b/web/src/common/ui/locale/index.ts deleted file mode 100644 index 30123df9e..000000000 --- a/web/src/common/ui/locale/index.ts +++ /dev/null @@ -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 }; diff --git a/web/src/common/users.ts b/web/src/common/users.ts index 994a09409..02e269895 100644 --- a/web/src/common/users.ts +++ b/web/src/common/users.ts @@ -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 { console.debug( `authentik/locale: Activating user's configured locale '${locale}'`, ); - activateLocale(locale); + window.dispatchEvent(customEvent(EVENT_LOCALE_REQUEST, { locale })); } return user; }) diff --git a/web/src/elements/Base.ts b/web/src/elements/Base.ts index f0e0b6108..3d38fc3ad 100644 --- a/web/src/elements/Base.ts +++ b/web/src/elements/Base.ts @@ -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 { diff --git a/web/src/elements/ak-locale-context/ak-locale-context.stories.ts b/web/src/elements/ak-locale-context/ak-locale-context.stories.ts new file mode 100644 index 000000000..8eaa91769 --- /dev/null +++ b/web/src/elements/ak-locale-context/ak-locale-context.stories.ts @@ -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`${msg("Everything is ok.")}`; + } +} + +@localized() +@customElement("ak-locale-sensitive-demo-component") +export class AKLocaleSensitiveDemoComponent extends LitElement { + render() { + return html`

${msg("Everything is ok.")}

`; + } +} + +export const InFrench = () => + html`
+ Everything is not ok. +
`; + +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`
+ + +
`; +}; diff --git a/web/src/elements/ak-locale-context/ak-locale-context.ts b/web/src/elements/ak-locale-context/ak-locale-context.ts new file mode 100644 index 000000000..304ddacac --- /dev/null +++ b/web/src/elements/ak-locale-context/ak-locale-context.ts @@ -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``; + } +} + +export default LocaleContext; diff --git a/web/src/elements/ak-locale-context/configureLocale.ts b/web/src/elements/ak-locale-context/configureLocale.ts new file mode 100644 index 000000000..21f244668 --- /dev/null +++ b/web/src/elements/ak-locale-context/configureLocale.ts @@ -0,0 +1,40 @@ +import { sourceLocale, targetLocales } from "@goauthentik/app/locale-codes"; + +import { configureLocalization } from "@lit/localize"; + +import { getBestMatchLocale } from "./helpers"; + +type LocaleGetter = ReturnType["getLocale"]; +type LocaleSetter = ReturnType["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 }; diff --git a/web/src/elements/ak-locale-context/context.ts b/web/src/elements/ak-locale-context/context.ts new file mode 100644 index 000000000..9c9903bb3 --- /dev/null +++ b/web/src/elements/ak-locale-context/context.ts @@ -0,0 +1,4 @@ +import { createContext } from "@lit-labs/context"; + +export const localeContext = createContext("locale"); +export default localeContext; diff --git a/web/src/common/ui/locale/definitions.ts b/web/src/elements/ak-locale-context/definitions.ts similarity index 100% rename from web/src/common/ui/locale/definitions.ts rename to web/src/elements/ak-locale-context/definitions.ts diff --git a/web/src/elements/ak-locale-context/helpers.ts b/web/src/elements/ak-locale-context/helpers.ts new file mode 100644 index 000000000..51ef76509 --- /dev/null +++ b/web/src/elements/ak-locale-context/helpers.ts @@ -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; diff --git a/web/src/elements/ak-locale-context/index.ts b/web/src/elements/ak-locale-context/index.ts new file mode 100644 index 000000000..1a264c6b3 --- /dev/null +++ b/web/src/elements/ak-locale-context/index.ts @@ -0,0 +1,4 @@ +import LocaleContext from "./ak-locale-context"; + +export { LocaleContext }; +export default LocaleContext; diff --git a/web/src/elements/ak-locale-context/types.ts b/web/src/elements/ak-locale-context/types.ts new file mode 100644 index 000000000..55147d247 --- /dev/null +++ b/web/src/elements/ak-locale-context/types.ts @@ -0,0 +1,10 @@ +import type { LocaleModule } from "@lit/localize"; + +export type LocaleRow = [string, RegExp, () => string, () => Promise]; + +export type AkLocale = { + code: string; + match: RegExp; + label: () => string; + locale: () => Promise; +}; diff --git a/web/src/elements/utils/customEvents.ts b/web/src/elements/utils/customEvents.ts new file mode 100644 index 000000000..f0dab467a --- /dev/null +++ b/web/src/elements/utils/customEvents.ts @@ -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; diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts index 49348b8e5..21121eac0 100644 --- a/web/src/flow/FlowExecutor.ts +++ b/web/src/flow/FlowExecutor.ts @@ -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`
${this.renderBackgroundOverlay()}
+ return html` +
${this.renderBackgroundOverlay()}
@@ -541,6 +543,7 @@ export class FlowExecutor extends Interface implements StageHost { ${until(this.renderInspector())}
-
`; +
+ `; } } diff --git a/web/src/flow/FlowInterface.ts b/web/src/flow/FlowInterface.ts index c46396e26..f645023c0 100644 --- a/web/src/flow/FlowInterface.ts +++ b/web/src/flow/FlowInterface.ts @@ -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(); diff --git a/web/src/flow/stages/prompt/PromptStage.ts b/web/src/flow/stages/prompt/PromptStage.ts index 1d074b2e0..1015737ed 100644 --- a/web/src/flow/stages/prompt/PromptStage.ts +++ b/web/src/flow/stages/prompt/PromptStage.ts @@ -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"; diff --git a/web/src/standalone/api-browser/index.ts b/web/src/standalone/api-browser/index.ts index 78c8d4c7c..b0c5849ed 100644 --- a/web/src/standalone/api-browser/index.ts +++ b/web/src/standalone/api-browser/index.ts @@ -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` - , - ) => { - e.detail.request.headers.append(CSRFHeaderName, getCookie("authentik_csrf")); - }} - > -
- -
-
+ + , + ) => { + e.detail.request.headers.append( + CSRFHeaderName, + getCookie("authentik_csrf"), + ); + }} + > +
+ +
+
+
`; } } diff --git a/web/src/standalone/loading/index.ts b/web/src/standalone/loading/index.ts index 9832cf970..907a05140 100644 --- a/web/src/standalone/loading/index.ts +++ b/web/src/standalone/loading/index.ts @@ -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[] { diff --git a/web/src/user/UserInterface.ts b/web/src/user/UserInterface.ts index c4cb19891..acd72e75f 100644 --- a/web/src/user/UserInterface.ts +++ b/web/src/user/UserInterface.ts @@ -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`
-
-
-
- - ${(this.tenant?.brandingTitle, DefaultTenant.brandingTitle)} - -
-
-
- ${this.uiConfig.enabledFeatures.apiDrawer - ? html`
- -
` - : html``} - ${this.uiConfig.enabledFeatures.notificationDrawer - ? html`
- -
` - : html``} - ${this.uiConfig.enabledFeatures.settings - ? html`
- - - -
` - : html``} -
- - - -
- ${this.me.user.isSuperuser - ? html` - ${msg("Admin interface")} - ` - : html``} + return html` +
+
+
+ - ${this.me.original - ? html`  -
-
- { - return new CoreApi(DEFAULT_CONFIG) - .coreUsersImpersonateEndRetrieve() - .then(() => { - window.location.reload(); - }); +
+
+ ${this.uiConfig.enabledFeatures.apiDrawer + ? html`
+ +
` + : html``} + ${this.uiConfig.enabledFeatures.notificationDrawer + ? html`
+ +
` + : html``} + ${this.uiConfig.enabledFeatures.settings + ? html`
+ + + +
` + : html``} +
+ + + +
+ ${this.me.user.isSuperuser + ? html` + ${msg("Admin interface")} + ` + : html``} +
+ ${this.me.original + ? html`` - : html``} -
-
- ${userDisplay} -
-
- ${msg( -
-
-
-
-
-
-
-
- - -
+ : html``} +
+
+ ${userDisplay}
- - + ${msg( +
+
+
+
+
+
+
+
+ + +
+
+
+ + +
-
`; + `; } } diff --git a/web/tsconfig.json b/web/tsconfig.json index 730eafbca..5480ffb45 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "strict": true, "paths": { + "@goauthentik/app/*": ["src/*"], "@goauthentik/admin/*": ["src/admin/*"], "@goauthentik/common/*": ["src/common/*"], "@goauthentik/docs/*": ["../website/docs/*"],