Merge branch 'main' into celery-2-dramatiq
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
207
web/package-lock.json
generated
207
web/package-lock.json
generated
@ -22,16 +22,17 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.7.11",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@goauthentik/api": "^2025.6.1-1749515784",
|
||||
"@goauthentik/api": "^2025.6.2-1750242193",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"@lit/task": "^1.0.2",
|
||||
"@mdx-js/mdx": "^3.1.0",
|
||||
"@mrmarble/djangoql-completion": "^0.8.3",
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^9.29.0",
|
||||
"@sentry/browser": "^9.30.0",
|
||||
"@spotlightjs/spotlight": "^3.0.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"change-case": "^5.4.4",
|
||||
@ -123,7 +124,7 @@
|
||||
"storybook-addon-mock": "^5.0.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.34.0",
|
||||
"typescript-eslint": "^8.34.1",
|
||||
"vite-plugin-lit-css": "^2.0.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wireit": "^0.14.12"
|
||||
@ -1728,9 +1729,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2025.6.1-1749515784",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.1-1749515784.tgz",
|
||||
"integrity": "sha512-0yN4vJ2/grtNz6OVNMW34gd6TylBeyTSoH1Zlr7e2yeAbg+oZB8WmpLLCZGqvOguYGN6vYEYrPQF1k3RJohmlQ=="
|
||||
"version": "2025.6.2-1750242193",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.2-1750242193.tgz",
|
||||
"integrity": "sha512-yAi3LbxGP1pyu/lV+iiJp2FrIhOa9/nMErdiy4KC2uh63AS74iqNpYb4/IIbkCeT8fbSSNKhAbA6gXE9zFZ6Yg=="
|
||||
},
|
||||
"node_modules/@goauthentik/core": {
|
||||
"resolved": "packages/core",
|
||||
@ -2660,6 +2661,16 @@
|
||||
"langium": "3.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@mrmarble/djangoql-completion": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@mrmarble/djangoql-completion/-/djangoql-completion-0.8.3.tgz",
|
||||
"integrity": "sha512-wYctvF0gQs48wL9jLQ+H2g2B0yJj7CrUSNi4ec5gcSuICIRqD/QSt6G+3zDdeW1LlI/4uj/FByJvg8k4TAAnVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lex": "1.7.9",
|
||||
"lodash": "4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/nice": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz",
|
||||
@ -4479,75 +4490,75 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sentry-internal/browser-utils": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.29.0.tgz",
|
||||
"integrity": "sha512-Wp6UJCDVV2KVK+TG8GwdLZyDy4GtUYDmVhGMpHKPS3G/Qgpf36cY/XHwChwaHZ5P9Bk1sjS9Ok698J59S8L2nw==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.30.0.tgz",
|
||||
"integrity": "sha512-e6ZlN8oWheCB0YJSGlBNUlh6UPnY5Ecj1P+/cgeKBhNm7c3bIx4J50485hB8LQsu+b7Q11L2o/wucZ//Pb6FCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.29.0"
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/feedback": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.29.0.tgz",
|
||||
"integrity": "sha512-ADvetGrtr+RfYcQKrQxah4fHs/xDJ/VjbStVMSuaNllzwWPYNkWIGFE6YjQ7wZszj0DQIu5/H+B6lZKsFYk4xw==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.30.0.tgz",
|
||||
"integrity": "sha512-qAZ7xxLqZM7GlEvmSUmTHnoueg+fc7esMQD4vH8pS7HI3n9C5MjGn3HHlndRpD8lL7iUUQ0TPZQgU6McbzMDyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.29.0"
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.29.0.tgz",
|
||||
"integrity": "sha512-we/1JPRje8sNowQCyogOV1OYWuDOP/3XmDi48XoFG2HB0XMl2HfL5LI8AvgAvC/5nrqVAAo4ktbjoVLm1fb7rg==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.30.0.tgz",
|
||||
"integrity": "sha512-+6wkqQGLJuFUzvGRzbh3iIhFGyxQx/Oxc0ODDKmz9ag2xYRjCYb3UUQXmQX9navAF0HXUsq8ajoJPm2L1ZyWVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.29.0",
|
||||
"@sentry/core": "9.29.0"
|
||||
"@sentry-internal/browser-utils": "9.30.0",
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay-canvas": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.29.0.tgz",
|
||||
"integrity": "sha512-TrQYhSAVPhyenvu0fNkon7BznFibu1mzS5bCudxhgOWajZluUVrXcbp8Q3WZ3R+AogrcgA3Vy6aumP/+fMKdwg==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.30.0.tgz",
|
||||
"integrity": "sha512-I4MxS27rfV7vnOU29L80y4baZ4I1XqpnYvC/yLN7C17nA8eDCufQ8WVomli41y8JETnfcxlm68z7CS0sO4RCSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "9.29.0",
|
||||
"@sentry/core": "9.29.0"
|
||||
"@sentry-internal/replay": "9.30.0",
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.29.0.tgz",
|
||||
"integrity": "sha512-+GFX/yb+rh6V1fSgTYM6ttAgledl2aUR3T3Rg86HNuegbdX8ym6lOtUOIZ0j9jPK015HR47KIPyIZVZZJ7Rj9g==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.30.0.tgz",
|
||||
"integrity": "sha512-sRyW6A9nIieTTI26MYXk1DmWEhmphTjZevusNWla+vvUigCmSjuH+xZw19w43OyvF3bu261Skypnm/mAalOTwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.29.0",
|
||||
"@sentry-internal/feedback": "9.29.0",
|
||||
"@sentry-internal/replay": "9.29.0",
|
||||
"@sentry-internal/replay-canvas": "9.29.0",
|
||||
"@sentry/core": "9.29.0"
|
||||
"@sentry-internal/browser-utils": "9.30.0",
|
||||
"@sentry-internal/feedback": "9.30.0",
|
||||
"@sentry-internal/replay": "9.30.0",
|
||||
"@sentry-internal/replay-canvas": "9.30.0",
|
||||
"@sentry/core": "9.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.29.0.tgz",
|
||||
"integrity": "sha512-wDyNe45PM+RCGtUn1tK7LzJ08ksv8i8KRUHrst7lsinEfRm83YH+wbWrPmwkVNEngUZvYkHwGLbNXM7xgFUuDQ==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.30.0.tgz",
|
||||
"integrity": "sha512-JfEpeQ8a1qVJEb9DxpFTFy1J1gkNdlgKAPiqYGNnm4yQbnfl2Kb/iEo1if70FkiHc52H8fJwISEF90pzMm6lPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@ -7345,17 +7356,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz",
|
||||
"integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.0",
|
||||
"@typescript-eslint/type-utils": "8.34.0",
|
||||
"@typescript-eslint/utils": "8.34.0",
|
||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/type-utils": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@ -7369,7 +7380,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.34.0",
|
||||
"@typescript-eslint/parser": "^8.34.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
@ -7385,16 +7396,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz",
|
||||
"integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
|
||||
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.34.0",
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"@typescript-eslint/typescript-estree": "8.34.0",
|
||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -7410,14 +7421,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz",
|
||||
"integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
|
||||
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.34.0",
|
||||
"@typescript-eslint/types": "^8.34.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.34.1",
|
||||
"@typescript-eslint/types": "^8.34.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -7432,14 +7443,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz",
|
||||
"integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
|
||||
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"@typescript-eslint/visitor-keys": "8.34.0"
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -7450,9 +7461,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz",
|
||||
"integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -7467,14 +7478,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz",
|
||||
"integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.34.0",
|
||||
"@typescript-eslint/utils": "8.34.0",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@ -7491,9 +7502,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz",
|
||||
"integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
|
||||
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -7505,16 +7516,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz",
|
||||
"integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
|
||||
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.34.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.34.0",
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"@typescript-eslint/visitor-keys": "8.34.0",
|
||||
"@typescript-eslint/project-service": "8.34.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -7534,16 +7545,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz",
|
||||
"integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
|
||||
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.0",
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"@typescript-eslint/typescript-estree": "8.34.0"
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -7558,14 +7569,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz",
|
||||
"integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
|
||||
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -18978,6 +18989,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lex": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/lex/-/lex-1.7.9.tgz",
|
||||
"integrity": "sha512-vzaalVBmFLnMaedq0QAsBAaXsWahzRpvnIBdBjj7y+7EKTS6lnziU2y/PsU2c6rV5qYj2B5IDw0uNJ9peXD0vw==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
@ -26933,15 +26950,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.34.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz",
|
||||
"integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==",
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz",
|
||||
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.34.0",
|
||||
"@typescript-eslint/parser": "8.34.0",
|
||||
"@typescript-eslint/utils": "8.34.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
||||
"@typescript-eslint/parser": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
||||
@ -93,16 +93,17 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.7.11",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@goauthentik/api": "^2025.6.1-1749515784",
|
||||
"@goauthentik/api": "^2025.6.2-1750242193",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"@lit/task": "^1.0.2",
|
||||
"@mdx-js/mdx": "^3.1.0",
|
||||
"@mrmarble/djangoql-completion": "^0.8.3",
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^9.29.0",
|
||||
"@sentry/browser": "^9.30.0",
|
||||
"@spotlightjs/spotlight": "^3.0.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"change-case": "^5.4.4",
|
||||
@ -194,7 +195,7 @@
|
||||
"storybook-addon-mock": "^5.0.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.34.0",
|
||||
"typescript-eslint": "^8.34.1",
|
||||
"vite-plugin-lit-css": "^2.0.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wireit": "^0.14.12"
|
||||
|
||||
2
web/packages/core/types/node.d.ts
vendored
2
web/packages/core/types/node.d.ts
vendored
@ -14,7 +14,7 @@ declare module "module" {
|
||||
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
||||
* ```
|
||||
*/
|
||||
// eslint-disable-next-line no-var
|
||||
|
||||
var __dirname: string;
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,8 @@ export class RecentEventsCard extends Table<Event> {
|
||||
}
|
||||
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
||||
html`<ak-empty-state
|
||||
><span slot="header">${msg("No Events found.")}</span>
|
||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
||||
@ -5,6 +5,7 @@ import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-file-input";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-slug-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/components/ak-textarea-input";
|
||||
@ -130,14 +131,14 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
required
|
||||
help=${msg("Application's display Name.")}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
help=${msg("Internal application name used in URLs.")}
|
||||
input-hint="code"
|
||||
></ak-text-input>
|
||||
></ak-slug-input>
|
||||
<ak-text-input
|
||||
name="group"
|
||||
value=${ifDefined(this.instance?.group)}
|
||||
|
||||
@ -117,13 +117,11 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
?invalid=${this.errors.has("name")}
|
||||
.errorMessages=${errors.name ?? this.errorMessages("name")}
|
||||
help=${msg("Application's display Name.")}
|
||||
id="ak-application-wizard-details-name"
|
||||
></ak-text-input>
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(app.slug)}
|
||||
label=${msg("Slug")}
|
||||
source="#ak-application-wizard-details-name"
|
||||
required
|
||||
?invalid=${errors.slug ?? this.errors.has("slug")}
|
||||
.errorMessages=${this.errorMessages("slug")}
|
||||
|
||||
@ -115,7 +115,8 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
||||
.columns=${COLUMNS}
|
||||
.content=${[]}
|
||||
></ak-select-table>
|
||||
<ak-empty-state header=${msg("No bound policies.")} icon="pf-icon-module">
|
||||
<ak-empty-state icon="pf-icon-module"
|
||||
><span slot="header">${msg("No bound policies.")} </span>
|
||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||
<div slot="primary">
|
||||
<button
|
||||
|
||||
@ -20,6 +20,7 @@ import { Event, EventsApi } from "@goauthentik/api";
|
||||
@customElement("ak-event-list")
|
||||
export class EventListPage extends TablePage<Event> {
|
||||
expandable = true;
|
||||
supportsQL = true;
|
||||
|
||||
pageTitle(): string {
|
||||
return msg("Event Log");
|
||||
|
||||
@ -135,7 +135,8 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state header=${msg("No Stages bound")} icon="pf-icon-module">
|
||||
html`<ak-empty-state icon="pf-icon-module">
|
||||
<span slot="header">${msg("No Stages bound")}</span>
|
||||
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
||||
<div slot="primary">
|
||||
<ak-stage-wizard
|
||||
|
||||
@ -3,6 +3,7 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/util
|
||||
import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes";
|
||||
import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-slug-input.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
@ -91,17 +92,16 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${msg("Shown as the Title in Flow pages.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.slug)}"
|
||||
class="pf-c-form-control pf-m-monospace"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${msg("Visible in the URL.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
help=${msg("Visible in the URL.")}
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
|
||||
<ak-form-element-horizontal label=${msg("Designation")} required name="designation">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.designation === undefined}>
|
||||
|
||||
@ -198,7 +198,8 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state header=${msg("No Policies bound.")} icon="pf-icon-module">
|
||||
html`<ak-empty-state icon="pf-icon-module"
|
||||
><span slot="header">${msg("No Policies bound.")}</span>
|
||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||
<div slot="primary">
|
||||
<ak-policy-wizard
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-secret-text-input.js";
|
||||
import "@goauthentik/components/ak-secret-textarea-input.js";
|
||||
import "@goauthentik/components/ak-slug-input.js";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import "@goauthentik/components/ak-textarea-input";
|
||||
@ -87,12 +88,13 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
value=${ifDefined(this.instance?.name)}
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
label=${msg("Slug")}
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
></ak-text-input>
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
<ak-switch-input
|
||||
name="enabled"
|
||||
?checked=${this.instance?.enabled ?? true}
|
||||
|
||||
@ -3,6 +3,7 @@ import { placeholderHelperText } from "@goauthentik/admin/helperText";
|
||||
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-secret-text-input.js";
|
||||
import "@goauthentik/components/ak-slug-input.js";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
@ -54,14 +55,15 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.slug)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
|
||||
<ak-form-element-horizontal name="enabled">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-secret-textarea-input.js";
|
||||
import "@goauthentik/components/ak-slug-input.js";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
@ -267,16 +268,13 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.slug)}"
|
||||
class="pf-c-form-control pf-m-monospace"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
<ak-form-element-horizontal name="enabled">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
|
||||
import { ascii_letters, digits, randomString } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-slug-input.js";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js";
|
||||
@ -183,14 +184,15 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.slug)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
|
||||
<ak-form-element-horizontal name="enabled">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
UserMatchingModeToLabel,
|
||||
} from "@goauthentik/admin/sources/oauth/utils";
|
||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-slug-input.js";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
@ -89,14 +90,15 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.slug)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
|
||||
<ak-form-element-horizontal name="enabled">
|
||||
<label class="pf-c-switch">
|
||||
<input
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { placeholderHelperText } from "@goauthentik/admin/helperText";
|
||||
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/components/ak-slug-input.js";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
@ -48,14 +49,15 @@ export class SCIMSourceForm extends BaseSourceForm<SCIMSource> {
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Slug")} required name="slug">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.slug)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
|
||||
<ak-form-element-horizontal name="enabled">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
|
||||
@ -41,14 +41,27 @@ export class InvitationForm extends ModelForm<Invitation, string> {
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal slugMode label=${msg("Name")} required name="name">
|
||||
const checkSlug = (ev: InputEvent) => {
|
||||
if (ev && ev.target && ev.target instanceof HTMLInputElement) {
|
||||
ev.target.value = (ev.target.value ?? "").replace(/[^a-z0-9-]/g, "");
|
||||
}
|
||||
};
|
||||
|
||||
return html` <ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||
<input
|
||||
type="text"
|
||||
id="admin-stages-invitation-name"
|
||||
value="${this.instance?.name || ""}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
@input=${(ev: InputEvent) => checkSlug(ev)}
|
||||
data-ak-slug="true"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg(
|
||||
"The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.",
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Expires")} required name="expires">
|
||||
<input
|
||||
|
||||
@ -85,6 +85,7 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
|
||||
expandable = true;
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
supportsQL = true;
|
||||
|
||||
searchEnabled(): boolean {
|
||||
return true;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { me } from "@goauthentik/common/users.js";
|
||||
import { isUserRoute } from "@goauthentik/elements/router/utils.js";
|
||||
import { deepmerge, deepmergeInto } from "deepmerge-ts";
|
||||
|
||||
import { UiThemeEnum, UserSelf } from "@goauthentik/api";
|
||||
import { CurrentBrand } from "@goauthentik/api";
|
||||
@ -96,13 +97,12 @@ export class DefaultUIConfig implements UIConfig {
|
||||
let globalUiConfig: Promise<UIConfig>;
|
||||
|
||||
export function getConfigForUser(user: UserSelf): UIConfig {
|
||||
const settings = user.settings;
|
||||
let config = new DefaultUIConfig();
|
||||
const settings = user.settings as UIConfig;
|
||||
const config = new DefaultUIConfig();
|
||||
if (!settings) {
|
||||
return config;
|
||||
}
|
||||
config = Object.assign(new DefaultUIConfig(), settings);
|
||||
return config;
|
||||
return deepmerge({ ...config }, settings);
|
||||
}
|
||||
|
||||
export function uiConfig(): Promise<UIConfig> {
|
||||
|
||||
306
web/src/components/ak-search-ql/index.ts
Normal file
306
web/src/components/ak-search-ql/index.ts
Normal file
@ -0,0 +1,306 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/buttons/Dropdown";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import DjangoQL, { Introspections } from "@mrmarble/djangoql-completion";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFSearchInput from "@patternfly/patternfly/components/SearchInput/search-input.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
export class QL extends DjangoQL {
|
||||
createCompletionElement() {
|
||||
this.completionEnabled = !!this.options.completionEnabled;
|
||||
return;
|
||||
}
|
||||
logError(message: string): void {
|
||||
console.warn(`authentik/ql: ${message}`);
|
||||
}
|
||||
textareaResize() {}
|
||||
}
|
||||
|
||||
@customElement("ak-search-ql")
|
||||
export class QLSearch extends AKElement {
|
||||
@property()
|
||||
value?: string;
|
||||
|
||||
@query("[name=search]")
|
||||
searchElement?: HTMLTextAreaElement;
|
||||
|
||||
@state()
|
||||
menuOpen = false;
|
||||
|
||||
@property()
|
||||
onSearch?: (value: string) => void;
|
||||
|
||||
@state()
|
||||
selected?: number;
|
||||
|
||||
@state()
|
||||
cursorX: number = 0;
|
||||
|
||||
@state()
|
||||
cursorY: number = 0;
|
||||
|
||||
ql?: QL;
|
||||
canvas?: CanvasRenderingContext2D;
|
||||
|
||||
set apiResponse(value: PaginatedResponse<unknown> | undefined) {
|
||||
if (!value || !value.autocomplete || !this.ql) {
|
||||
return;
|
||||
}
|
||||
this.ql.loadIntrospections(value.autocomplete as unknown as Introspections);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFFormControl,
|
||||
PFSearchInput,
|
||||
css`
|
||||
::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
.ql.pf-c-form-control {
|
||||
font-family: monospace;
|
||||
resize: vertical;
|
||||
height: 2.25em;
|
||||
}
|
||||
.selected {
|
||||
background-color: var(--pf-c-search-input__menu-item--hover--BackgroundColor);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__menu {
|
||||
--pf-c-search-input__menu--BackgroundColor: var(--ak-dark-background-light-ish);
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__menu-item {
|
||||
--pf-c-search-input__menu-item--Color: var(--ak-dark-foreground);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__menu-item:hover {
|
||||
--pf-c-search-input__menu-item--BackgroundColor: var(
|
||||
--ak-dark-background-lighter
|
||||
);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__menu-list-item.selected {
|
||||
--pf-c-search-input__menu-item--hover--BackgroundColor: var(
|
||||
--ak-dark-background-light
|
||||
);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__text::before {
|
||||
border: 0;
|
||||
}
|
||||
.pf-c-search-input__menu {
|
||||
position: fixed;
|
||||
min-width: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
if (!this.searchElement) {
|
||||
return;
|
||||
}
|
||||
this.ql = new QL({
|
||||
completionEnabled: true,
|
||||
introspections: {
|
||||
current_model: "",
|
||||
models: {},
|
||||
},
|
||||
selector: this.searchElement,
|
||||
autoResize: false,
|
||||
});
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
console.error("authentik/ql: failed to get canvas context");
|
||||
return;
|
||||
}
|
||||
context.font = window.getComputedStyle(this.searchElement).font;
|
||||
this.canvas = context;
|
||||
}
|
||||
|
||||
refreshCompletions() {
|
||||
this.value = this.searchElement?.value;
|
||||
if (!this.ql) {
|
||||
return;
|
||||
}
|
||||
this.ql.generateSuggestions();
|
||||
if (this.ql.suggestions.length < 1 || this.ql.loading) {
|
||||
this.menuOpen = false;
|
||||
return;
|
||||
}
|
||||
this.menuOpen = true;
|
||||
this.updateDropdownPosition();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
updateDropdownPosition() {
|
||||
if (!this.searchElement) {
|
||||
return;
|
||||
}
|
||||
const bcr = this.getBoundingClientRect();
|
||||
// We need the width of a letter to measure x; we use a monospaced font but still
|
||||
// check the length for `m` as its the widest ASCII char
|
||||
const metrics = this.canvas?.measureText("m");
|
||||
const letterWidth = Math.ceil(metrics?.width || 0) + 1;
|
||||
|
||||
// Mostly static variables for padding, font line-height and how many
|
||||
const lineHeight = parseInt(window.getComputedStyle(this.searchElement).lineHeight, 10);
|
||||
const paddingTop = parseInt(window.getComputedStyle(this.searchElement).paddingTop, 10);
|
||||
const paddingLeft = parseInt(window.getComputedStyle(this.searchElement).paddingLeft, 10);
|
||||
const paddingRight = parseInt(window.getComputedStyle(this.searchElement).paddingRight, 10);
|
||||
const actualInnerWidth = bcr.width - paddingLeft - paddingRight;
|
||||
|
||||
let relX = 0;
|
||||
let relY = 1;
|
||||
let letterIndex = 0;
|
||||
|
||||
this.searchElement.value.split(" ").some((word, idx) => {
|
||||
letterIndex += word.length;
|
||||
const newRelX = relX + word.length * letterWidth;
|
||||
if (newRelX > actualInnerWidth) {
|
||||
relY += 1;
|
||||
if (letterIndex > this.searchElement!.selectionStart) {
|
||||
relX =
|
||||
letterWidth * word.length -
|
||||
(letterIndex - this.searchElement!.selectionStart) * letterWidth;
|
||||
return true;
|
||||
}
|
||||
relX = word.length * letterWidth;
|
||||
} else {
|
||||
relX = newRelX + 1;
|
||||
}
|
||||
});
|
||||
|
||||
this.cursorX = bcr.x + paddingLeft + relX;
|
||||
this.cursorY = bcr.y + paddingTop + relY * lineHeight;
|
||||
}
|
||||
|
||||
onKeyDown(ev: KeyboardEvent) {
|
||||
this.updateDropdownPosition();
|
||||
if (ev.key === "Enter" && ev.metaKey && this.onSearch && this.searchElement) {
|
||||
this.onSearch(this.searchElement?.value);
|
||||
return;
|
||||
}
|
||||
if (!this.menuOpen) return;
|
||||
switch (ev.key) {
|
||||
case "ArrowUp":
|
||||
if (this.ql?.suggestions.length) {
|
||||
if (this.selected === undefined) {
|
||||
this.selected = this.ql?.suggestions.length - 1;
|
||||
} else if (this.selected === 0) {
|
||||
this.selected = undefined;
|
||||
} else {
|
||||
this.selected -= 1;
|
||||
}
|
||||
this.refreshCompletions();
|
||||
ev.preventDefault();
|
||||
}
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (this.ql?.suggestions.length) {
|
||||
if (this.selected === undefined) {
|
||||
this.selected = 0;
|
||||
} else if (this.selected < this.ql?.suggestions.length - 1) {
|
||||
this.selected += 1;
|
||||
} else {
|
||||
this.selected = undefined;
|
||||
}
|
||||
this.refreshCompletions();
|
||||
ev.preventDefault();
|
||||
}
|
||||
break;
|
||||
case "Tab":
|
||||
if (this.selected) {
|
||||
this.ql?.selectCompletion(this.selected);
|
||||
ev.preventDefault();
|
||||
}
|
||||
break;
|
||||
case "Enter":
|
||||
// Technically this is a textarea, due to automatic multi-line feature,
|
||||
// but other than that it should look and behave like a normal input.
|
||||
// So expected behavior when pressing Enter is to submit the form,
|
||||
// not to add a new line.
|
||||
if (this.selected !== undefined) {
|
||||
this.ql?.selectCompletion(this.selected);
|
||||
}
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case "Escape":
|
||||
this.menuOpen = false;
|
||||
break;
|
||||
case "Shift": // Shift
|
||||
case "Control": // Ctrl
|
||||
case "Alt": // Alt
|
||||
case "Meta": // Windows Key or Cmd on Mac
|
||||
// Control keys shouldn't trigger completion popup
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renderMenu() {
|
||||
if (!this.menuOpen || !this.ql) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<div
|
||||
class="pf-c-search-input__menu"
|
||||
style="left: ${this.cursorX}px; top: ${this.cursorY}px;"
|
||||
>
|
||||
<ul class="pf-c-search-input__menu-list">
|
||||
${this.ql.suggestions.map((suggestion, idx) => {
|
||||
return html`<li
|
||||
class="pf-c-search-input__menu-list-item ${this.selected === idx
|
||||
? "selected"
|
||||
: ""}"
|
||||
>
|
||||
<button
|
||||
class="pf-c-search-input__menu-item"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.ql?.selectCompletion(idx);
|
||||
this.refreshCompletions();
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-search-input__menu-item-text"
|
||||
>${suggestion.text}</span
|
||||
>
|
||||
</button>
|
||||
</li>`;
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-search-input">
|
||||
<div class="pf-c-search-input__bar">
|
||||
<span class="pf-c-search-input__text">
|
||||
<textarea
|
||||
class="pf-c-form-control ql"
|
||||
name="search"
|
||||
placeholder=${msg("Search...")}
|
||||
spellcheck="false"
|
||||
@input=${(ev: InputEvent) => this.refreshCompletions()}
|
||||
@keydown=${this.onKeyDown}
|
||||
>
|
||||
${ifDefined(this.value)}</textarea
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
${this.renderMenu()}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-search-ql": QLSearch;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import { formatSlug } from "@goauthentik/elements/router/utils.js";
|
||||
import { bound } from "@goauthentik/elements/decorators/bound.js";
|
||||
import { kebabCase } from "change-case";
|
||||
|
||||
import { html } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
@ -6,59 +7,83 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { HorizontalLightComponent } from "./HorizontalLightComponent";
|
||||
|
||||
const slugify = (s: string) => kebabCase(s, { suffixCharacters: "-" });
|
||||
|
||||
/**
|
||||
* @element ak-slug-input
|
||||
* @class AkSlugInput
|
||||
*
|
||||
* A wrapper around `ak-form-element-horizontal` and a text input control that listens for input on
|
||||
* a peer text input control and automatically mirrors that control's value, transforming the value
|
||||
* into a slug and displaying it separately.
|
||||
*
|
||||
* If the user manually changes the slug, mirroring and transformation stop. If, after that, both
|
||||
* fields are cleared manually, mirroring and transformation resume.
|
||||
*
|
||||
* ## Limitations:
|
||||
*
|
||||
* Both the source text field and the slug field must be rendered in the same render pass (i.e.,
|
||||
* part of the same singular call to a `render` function) so that the slug field can find its
|
||||
* source.
|
||||
*
|
||||
* For the same reason, both the source text field and the slug field must share the same immediate
|
||||
* parent DOM object.
|
||||
*
|
||||
* Since we expect the source text field and the slug to be part of the same form and rendered not
|
||||
* just in the same form but in the same form group, these are not considered burdensome
|
||||
* restrictions.
|
||||
*/
|
||||
@customElement("ak-slug-input")
|
||||
export class AkSlugInput extends HorizontalLightComponent<string> {
|
||||
@property({ type: String, reflect: true })
|
||||
value = "";
|
||||
|
||||
/**
|
||||
* A selector indicating the source text input control. Must be unique within the whole DOM
|
||||
* context of the slug and source controls. The most common use in authentik is the default:
|
||||
* slugifying the "name" of something.
|
||||
*/
|
||||
@property({ type: String })
|
||||
source = "";
|
||||
public source = "[name='name']";
|
||||
|
||||
origin?: HTMLInputElement | null;
|
||||
@property({ type: String, reflect: true })
|
||||
public value = "";
|
||||
|
||||
@query("input")
|
||||
input!: HTMLInputElement;
|
||||
private input!: HTMLInputElement;
|
||||
|
||||
touched: boolean = false;
|
||||
#origin?: HTMLInputElement | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.slugify = this.slugify.bind(this);
|
||||
this.handleTouch = this.handleTouch.bind(this);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.input.addEventListener("input", this.handleTouch);
|
||||
}
|
||||
#touched: boolean = false;
|
||||
|
||||
// Do not stop propagation of this event; it must be sent up the tree so that a parent
|
||||
// component, such as a custom forms manager, may receive it.
|
||||
handleTouch(ev: Event) {
|
||||
this.input.value = formatSlug(this.input.value);
|
||||
this.value = this.input.value;
|
||||
protected handleTouch(ev: Event) {
|
||||
this.value = this.input.value = slugify(this.input.value);
|
||||
|
||||
if (this.origin && this.origin.value === "" && this.input.value === "") {
|
||||
this.touched = false;
|
||||
// Reset 'touched' status if the slug & target have been reset
|
||||
if (this.#origin && this.#origin.value === "" && this.input.value === "") {
|
||||
this.#touched = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev && ev.target && ev.target instanceof HTMLInputElement) {
|
||||
this.touched = true;
|
||||
this.#touched = true;
|
||||
}
|
||||
}
|
||||
|
||||
slugify(ev: Event) {
|
||||
@bound
|
||||
protected slugify(ev: Event) {
|
||||
if (!(ev && ev.target && ev.target instanceof HTMLInputElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset 'touched' status if the slug & target have been reset
|
||||
if (ev.target.value === "" && this.input.value === "") {
|
||||
this.touched = false;
|
||||
this.#touched = false;
|
||||
}
|
||||
|
||||
// Don't proceed if the user has hand-modified the slug
|
||||
if (this.touched) {
|
||||
// Don't proceed if the user has hand-modified the slug. (Note the order of statements: if
|
||||
// the user hand modified the slug to be empty as part of resetting the slug/source
|
||||
// relationship, that's a "not-touched" condition and falls through.)
|
||||
if (this.#touched) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -67,7 +92,7 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
|
||||
// "any event which adds or removes a character but leaves the rest of the slug looking like
|
||||
// the previous iteration, set it to the current iteration."
|
||||
|
||||
const newSlug = formatSlug(ev.target.value);
|
||||
const newSlug = slugify(ev.target.value);
|
||||
const oldSlug = this.input.value;
|
||||
const [shorter, longer] =
|
||||
newSlug.length < oldSlug.length ? [newSlug, oldSlug] : [oldSlug, newSlug];
|
||||
@ -81,7 +106,6 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
|
||||
// to listeners, both the name and value of the host must match those of the target
|
||||
// input. The name is already handled since it's both required and automatically
|
||||
// forwarded to our templated input, but the value must also be set.
|
||||
|
||||
this.value = this.input.value = newSlug;
|
||||
this.dispatchEvent(
|
||||
new Event("input", {
|
||||
@ -91,38 +115,36 @@ export class AkSlugInput extends HorizontalLightComponent<string> {
|
||||
);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// Set up listener on source element, so we can slugify the content.
|
||||
setTimeout(() => {
|
||||
if (this.source) {
|
||||
const rootNode = this.getRootNode();
|
||||
if (rootNode instanceof ShadowRoot || rootNode instanceof Document) {
|
||||
this.origin = rootNode.querySelector(this.source);
|
||||
}
|
||||
if (this.origin) {
|
||||
this.origin.addEventListener("input", this.slugify);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this.origin) {
|
||||
this.origin.removeEventListener("input", this.slugify);
|
||||
public override disconnectedCallback() {
|
||||
if (this.#origin) {
|
||||
this.#origin.removeEventListener("input", this.slugify);
|
||||
}
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
renderControl() {
|
||||
public override renderControl() {
|
||||
return html`<input
|
||||
@input=${(ev: Event) => this.handleTouch(ev)}
|
||||
type="text"
|
||||
value=${ifDefined(this.value)}
|
||||
class="pf-c-form-control"
|
||||
?required=${this.required}
|
||||
/>`;
|
||||
}
|
||||
|
||||
public override firstUpdated() {
|
||||
if (!this.source) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootNode = this.getRootNode();
|
||||
if (rootNode instanceof ShadowRoot || rootNode instanceof Document) {
|
||||
this.#origin = rootNode.querySelector(this.source);
|
||||
}
|
||||
if (this.#origin) {
|
||||
this.#origin.addEventListener("input", this.slugify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AkSlugInput;
|
||||
|
||||
@ -94,7 +94,8 @@ export class ObjectChangelog extends Table<Event> {
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
||||
html`<ak-empty-state
|
||||
><span slot="header">${msg("No Events found.")}</span>
|
||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
||||
@ -66,7 +66,8 @@ export class UserEvents extends Table<Event> {
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
||||
html`<ak-empty-state
|
||||
><span slot="header">${msg("No Events found.")}</span>
|
||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
||||
@ -3,6 +3,7 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/Spinner";
|
||||
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
import { SlotController } from "@patternfly/pfe-core/controllers/slot-controller.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, html, nothing } from "lit";
|
||||
@ -33,6 +34,8 @@ export class EmptyState extends AKElement implements IEmptyState {
|
||||
@property()
|
||||
header?: string;
|
||||
|
||||
slots = new SlotController(this, "header", "body", "primary");
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
@ -48,6 +51,12 @@ export class EmptyState extends AKElement implements IEmptyState {
|
||||
}
|
||||
|
||||
render() {
|
||||
const showHeader = this.loading || this.slots.hasSlotted("header");
|
||||
const header = () =>
|
||||
this.slots.hasSlotted("header")
|
||||
? html`<slot name="header"></slot>`
|
||||
: html`<span>${msg("Loading")}</span>`;
|
||||
|
||||
return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}">
|
||||
<div class="pf-c-empty-state__content">
|
||||
${this.loading
|
||||
@ -59,15 +68,17 @@ export class EmptyState extends AKElement implements IEmptyState {
|
||||
"fa-question-circle"} pf-c-empty-state__icon"
|
||||
aria-hidden="true"
|
||||
></i>`}
|
||||
<h1 class="pf-c-title pf-m-lg">
|
||||
${this.loading && this.header === undefined ? msg("Loading") : this.header}
|
||||
</h1>
|
||||
<div class="pf-c-empty-state__body">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<div class="pf-c-empty-state__primary">
|
||||
<slot name="primary"></slot>
|
||||
</div>
|
||||
${showHeader ? html` <h1 class="pf-c-title pf-m-lg">${header()}</h1>` : nothing}
|
||||
${this.slots.hasSlotted("body")
|
||||
? html` <div class="pf-c-empty-state__body">
|
||||
<slot name="body"></slot>
|
||||
</div>`
|
||||
: nothing}
|
||||
${this.slots.hasSlotted("primary")
|
||||
? html` <div class="pf-c-empty-state__primary">
|
||||
<slot name="primary"></slot>
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@ -200,7 +200,8 @@ export abstract class AKChart<T> extends AKElement {
|
||||
<div class="container">
|
||||
${this.error
|
||||
? html`
|
||||
<ak-empty-state header="${msg("Failed to fetch data.")}" icon="fa-times">
|
||||
<ak-empty-state icon="fa-times"
|
||||
><span slot="header">${msg("Failed to fetch data.")}</span>
|
||||
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
||||
</ak-empty-state>
|
||||
`
|
||||
|
||||
@ -40,7 +40,9 @@ export class LogViewer extends Table<LogEvent> {
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state header=${msg("No log messages.")}> </ak-empty-state>`,
|
||||
html`<ak-empty-state
|
||||
><span slot="header">${msg("No log messages.")}</span>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers";
|
||||
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
|
||||
import { formatSlug } from "@goauthentik/elements/router/utils.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
@ -197,39 +196,6 @@ export abstract class Form<T> extends AKElement {
|
||||
return this.successMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* After rendering the form, if there is both a `name` and `slug` element within the form,
|
||||
* events the `name` element so that the slug will always have a slugified version of the
|
||||
* `name.`. This duplicates functionality within ak-form-element-horizontal.
|
||||
*/
|
||||
updated(): void {
|
||||
this.shadowRoot
|
||||
?.querySelectorAll("ak-form-element-horizontal[name=name]")
|
||||
.forEach((nameInput) => {
|
||||
const input = nameInput.firstElementChild as HTMLInputElement;
|
||||
const form = nameInput.closest("form");
|
||||
if (form === null) {
|
||||
return;
|
||||
}
|
||||
const slugFieldWrapper = form.querySelector(
|
||||
"ak-form-element-horizontal[name=slug]",
|
||||
);
|
||||
if (!slugFieldWrapper) {
|
||||
return;
|
||||
}
|
||||
const slugField = slugFieldWrapper.firstElementChild as HTMLInputElement;
|
||||
// Only attach handler if the slug is already equal to the name
|
||||
// if not, they are probably completely different and shouldn't update
|
||||
// each other
|
||||
if (formatSlug(input.value) !== slugField.value) {
|
||||
return;
|
||||
}
|
||||
nameInput.addEventListener("input", () => {
|
||||
slugField.value = formatSlug(input.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resetForm(): void {
|
||||
const form = this.shadowRoot?.querySelector<HTMLFormElement>("form");
|
||||
form?.reset();
|
||||
|
||||
@ -77,9 +77,6 @@ export class HorizontalFormElement extends AKElement {
|
||||
@property({ attribute: false })
|
||||
errorMessages: string[] | string[][] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
slugMode = false;
|
||||
|
||||
_invalid = false;
|
||||
|
||||
/* If this property changes, we want to make sure the parent control is "opened" so
|
||||
@ -109,13 +106,6 @@ export class HorizontalFormElement extends AKElement {
|
||||
this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => {
|
||||
input.focus();
|
||||
});
|
||||
if (this.name === "slug" || this.slugMode) {
|
||||
this.querySelectorAll<HTMLInputElement>("input[type='text']").forEach((input) => {
|
||||
input.addEventListener("keyup", () => {
|
||||
input.value = formatSlug(input.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.querySelectorAll("*").forEach((input) => {
|
||||
if (isAkControl(input) && !input.getAttribute("name")) {
|
||||
input.setAttribute("name", this.name);
|
||||
|
||||
@ -163,7 +163,8 @@ export class NotificationDrawer extends AKElement {
|
||||
}
|
||||
|
||||
renderEmpty() {
|
||||
return html`<ak-empty-state header=${msg("No notifications found.")}>
|
||||
return html`<ak-empty-state
|
||||
><span slot="header">${msg("No notifications found.")}</span>
|
||||
<div slot="body">${msg("You don't have any notifications currently.")}</div>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { WithLicenseSummary } from "#elements/mixins/license";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import {
|
||||
APIError,
|
||||
@ -31,13 +32,20 @@ import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";
|
||||
import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { Pagination } from "@goauthentik/api";
|
||||
import { LicenseSummaryStatusEnum, Pagination } from "@goauthentik/api";
|
||||
|
||||
export interface TableLike {
|
||||
order?: string;
|
||||
fetch: () => void;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
pagination: Pagination;
|
||||
autocomplete?: { [key: string]: string };
|
||||
|
||||
results: Array<T>;
|
||||
}
|
||||
|
||||
export class TableColumn {
|
||||
title: string;
|
||||
orderBy?: string;
|
||||
@ -94,19 +102,16 @@ export class TableColumn {
|
||||
}
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
pagination: Pagination;
|
||||
|
||||
results: Array<T>;
|
||||
}
|
||||
|
||||
export abstract class Table<T> extends AKElement implements TableLike {
|
||||
export abstract class Table<T> extends WithLicenseSummary(AKElement) implements TableLike {
|
||||
abstract apiEndpoint(): Promise<PaginatedResponse<T>>;
|
||||
abstract columns(): TableColumn[];
|
||||
abstract row(item: T): SlottedTemplateResult[];
|
||||
|
||||
private isLoading = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
supportsQL: boolean = false;
|
||||
|
||||
searchEnabled(): boolean {
|
||||
return false;
|
||||
}
|
||||
@ -181,6 +186,12 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
||||
PFDropdown,
|
||||
PFPagination,
|
||||
css`
|
||||
.pf-c-toolbar__group.pf-m-search-filter.ql {
|
||||
flex-grow: 1;
|
||||
}
|
||||
ak-table-search.ql {
|
||||
width: 100% !important;
|
||||
}
|
||||
.pf-c-table thead .pf-c-table__check {
|
||||
min-width: 3rem;
|
||||
}
|
||||
@ -288,7 +299,9 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
||||
return html`<tr role="row">
|
||||
<td role="cell" colspan="25">
|
||||
<div class="pf-l-bullseye">
|
||||
<ak-empty-state loading header=${msg("Loading")}></ak-empty-state>
|
||||
<ak-empty-state loading
|
||||
><span slot="header">${msg("Loading")}</span></ak-empty-state
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
@ -300,8 +313,9 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
||||
<td role="cell" colspan="8">
|
||||
<div class="pf-l-bullseye">
|
||||
${inner ??
|
||||
html`<ak-empty-state header="${msg("No objects found.")}"
|
||||
><div slot="primary">${this.renderObjectCreate()}</div>
|
||||
html`<ak-empty-state
|
||||
><span slot="header">${msg("No objects found.")}</span> >
|
||||
<div slot="primary">${this.renderObjectCreate()}</div>
|
||||
</ak-empty-state>`}
|
||||
</div>
|
||||
</td>
|
||||
@ -316,7 +330,8 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
||||
renderError(): SlottedTemplateResult {
|
||||
if (!this.error) return nothing;
|
||||
|
||||
return html`<ak-empty-state header="${msg("Failed to fetch objects.")}" icon="fa-ban">
|
||||
return html`<ak-empty-state icon="fa-ban"
|
||||
><span slot="header">${msg("Failed to fetch objects.")}</span>
|
||||
<div slot="body">${pluckErrorDetail(this.error)}</div>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
@ -470,14 +485,17 @@ export abstract class Table<T> extends AKElement implements TableLike {
|
||||
});
|
||||
this.fetch();
|
||||
};
|
||||
|
||||
const isQL =
|
||||
this.supportsQL && this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed;
|
||||
return !this.searchEnabled()
|
||||
? html``
|
||||
: html`<div class="pf-c-toolbar__group pf-m-search-filter">
|
||||
: html`<div class="pf-c-toolbar__group pf-m-search-filter ${isQL ? "ql" : ""}">
|
||||
<ak-table-search
|
||||
class="pf-c-toolbar__item pf-m-search-filter"
|
||||
?supportsQL=${this.supportsQL}
|
||||
class="pf-c-toolbar__item pf-m-search-filter ${isQL ? "ql" : ""}"
|
||||
value=${ifDefined(this.search)}
|
||||
.onSearch=${runSearch}
|
||||
.apiResponse=${this.data}
|
||||
>
|
||||
</ak-table-search>
|
||||
</div>`;
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { WithLicenseSummary } from "#elements/mixins/license";
|
||||
import "@goauthentik/components/ak-search-ql";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
@ -11,11 +14,19 @@ import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-gro
|
||||
import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { LicenseSummaryStatusEnum } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-table-search")
|
||||
export class TableSearch extends AKElement {
|
||||
export class TableSearch extends WithLicenseSummary(AKElement) {
|
||||
@property()
|
||||
value?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
supportsQL: boolean = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
apiResponse?: PaginatedResponse<unknown>;
|
||||
|
||||
@property()
|
||||
onSearch?: (value: string) => void;
|
||||
|
||||
@ -30,39 +41,63 @@ export class TableSearch extends AKElement {
|
||||
::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
ak-search-ql {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
renderInput(): TemplateResult {
|
||||
if (
|
||||
this.supportsQL &&
|
||||
this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed
|
||||
) {
|
||||
return html`<ak-search-ql
|
||||
.apiResponse=${this.apiResponse}
|
||||
.value=${this.value}
|
||||
.onSearch=${(value: string) => {
|
||||
if (!this.onSearch) return;
|
||||
this.onSearch(value);
|
||||
}}
|
||||
name="search"
|
||||
></ak-search-ql>`;
|
||||
}
|
||||
return html`<input
|
||||
class="pf-c-form-control"
|
||||
name="search"
|
||||
type="search"
|
||||
placeholder=${msg("Search...")}
|
||||
value="${ifDefined(this.value)}"
|
||||
@search=${(ev: Event) => {
|
||||
if (!this.onSearch) return;
|
||||
this.onSearch((ev.target as HTMLInputElement).value);
|
||||
}}
|
||||
/>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form
|
||||
class="pf-c-input-group"
|
||||
method="GET"
|
||||
method="get"
|
||||
@submit=${(e: Event) => {
|
||||
e.preventDefault();
|
||||
if (!this.onSearch) return;
|
||||
const el = this.shadowRoot?.querySelector<HTMLInputElement>("input[type=search]");
|
||||
const el = this.shadowRoot?.querySelector<HTMLInputElement | HTMLTextAreaElement>(
|
||||
"[name=search]",
|
||||
);
|
||||
if (!el) return;
|
||||
if (el.value === "") return;
|
||||
this.onSearch(el?.value);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
name="search"
|
||||
type="search"
|
||||
placeholder=${msg("Search...")}
|
||||
value="${ifDefined(this.value)}"
|
||||
@search=${(ev: Event) => {
|
||||
if (!this.onSearch) return;
|
||||
this.onSearch((ev.target as HTMLInputElement).value);
|
||||
}}
|
||||
/>
|
||||
${this.renderInput()}
|
||||
<button
|
||||
class="pf-c-button pf-m-control"
|
||||
type="reset"
|
||||
@click=${() => {
|
||||
if (!this.onSearch) return;
|
||||
this.value = "";
|
||||
this.onSearch("");
|
||||
}}
|
||||
>
|
||||
|
||||
@ -19,7 +19,11 @@ describe("ak-empty-state", () => {
|
||||
});
|
||||
|
||||
it("should render the default loader", async () => {
|
||||
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
||||
render(
|
||||
html`<ak-empty-state loading
|
||||
><span slot="header">${msg("Loading")}</span>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
@ -29,7 +33,11 @@ describe("ak-empty-state", () => {
|
||||
});
|
||||
|
||||
it("should handle standard boolean", async () => {
|
||||
render(html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`);
|
||||
render(
|
||||
html`<ak-empty-state loading
|
||||
><span slot="header">${msg("Loading")}</span>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
@ -39,7 +47,11 @@ describe("ak-empty-state", () => {
|
||||
});
|
||||
|
||||
it("should render a static empty state", async () => {
|
||||
render(html`<ak-empty-state header=${msg("No messages found")}> </ak-empty-state>`);
|
||||
render(
|
||||
html`<ak-empty-state
|
||||
><span slot="header">${msg("No messages found")}</span>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
@ -51,7 +63,8 @@ describe("ak-empty-state", () => {
|
||||
|
||||
it("should render a slotted message", async () => {
|
||||
render(
|
||||
html`<ak-empty-state header=${msg("No messages found")}>
|
||||
html`<ak-empty-state
|
||||
><span slot="header">${msg("No messages found")}</span>
|
||||
<p slot="body">Try again with a different filter</p>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
||||
2
web/types/node.d.ts
vendored
2
web/types/node.d.ts
vendored
@ -14,7 +14,7 @@ declare module "module" {
|
||||
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
||||
* ```
|
||||
*/
|
||||
// eslint-disable-next-line no-var
|
||||
|
||||
var __dirname: string;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9245,6 +9245,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -7753,6 +7753,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9305,6 +9305,9 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9874,6 +9874,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9857,6 +9857,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9213,6 +9213,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9117,6 +9117,9 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9540,6 +9540,9 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9549,4 +9549,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body></file></xliff>
|
||||
|
||||
@ -9632,6 +9632,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9604,6 +9604,9 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -6368,6 +6368,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@ -9878,12 +9878,18 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf9686d31d28fcf7d">
|
||||
<source>Show field content</source>
|
||||
<target>显示字段内容</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb1b05a7573ab618c">
|
||||
<source>Hide field content</source>
|
||||
<target>隐藏字段内容</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
<target>使用 Plex 重新验证身份</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -7453,6 +7453,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -3011,11 +3011,6 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
<source>Load servers</source>
|
||||
<target>加载服务器</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s24f405197ede5ebb">
|
||||
<source>Re-authenticate with plex</source>
|
||||
<target>使用 Plex 重新验证身份</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sc297b2e13c28ecf9">
|
||||
<source>Allow friends to authenticate via Plex, even if you don't share any servers</source>
|
||||
@ -9880,6 +9875,18 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="sb3d5c0a0501669df">
|
||||
<source>Generate New Certificate-Key Pair</source>
|
||||
<target>生成新的证书密钥对</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf9686d31d28fcf7d">
|
||||
<source>Show field content</source>
|
||||
<target>显示字段内容</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb1b05a7573ab618c">
|
||||
<source>Hide field content</source>
|
||||
<target>隐藏字段内容</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
<target>使用 Plex 重新验证身份</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@ -9192,6 +9192,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f820625804ed29b">
|
||||
<source>Re-authenticate with Plex</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0433d667ea6eec1a">
|
||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
Reference in New Issue
Block a user