Compare commits
15 Commits
version-20
...
root/remov
Author | SHA1 | Date | |
---|---|---|---|
c255ec7e71 | |||
b23a6d5359 | |||
212904537b | |||
c46cd5e7e5 | |||
480a765f5f | |||
65b6ea416c | |||
2f56c3cecf | |||
5c1432c670 | |||
c49e83a926 | |||
0dc2c46d49 | |||
245d8b7b5c | |||
5884af8af1 | |||
08d52be20d | |||
941f05e7fa | |||
493cefaa6e |
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -172,7 +172,7 @@ jobs:
|
||||
run: |
|
||||
docker-compose -f tests/e2e/docker-compose.yml up -d
|
||||
- id: cache-web
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: web/dist
|
||||
key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }}
|
||||
|
29
Dockerfile
29
Dockerfile
@ -1,24 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Stage 1: Build website
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as website-builder
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /work/website
|
||||
|
||||
RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.json \
|
||||
--mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \
|
||||
--mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \
|
||||
npm ci --include=dev
|
||||
|
||||
COPY ./website /work/website/
|
||||
COPY ./blueprints /work/blueprints/
|
||||
COPY ./SECURITY.md /work/
|
||||
|
||||
RUN npm run build-docs-only
|
||||
|
||||
# Stage 2: Build webui
|
||||
# Stage 1: Build webui
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder
|
||||
|
||||
ENV NODE_ENV=production
|
||||
@ -36,7 +18,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Stage 3: Build go proxy
|
||||
# Stage 2: Build go proxy
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.6-bookworm AS go-builder
|
||||
|
||||
ARG TARGETOS
|
||||
@ -68,7 +50,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
|
||||
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
|
||||
GOARM="${TARGETVARIANT#v}" go build -o /go/authentik ./cmd/server
|
||||
|
||||
# Stage 4: MaxMind GeoIP
|
||||
# Stage 3: MaxMind GeoIP
|
||||
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.1 as geoip
|
||||
|
||||
ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN"
|
||||
@ -82,7 +64,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
mkdir -p /usr/share/GeoIP && \
|
||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 5: Python dependencies
|
||||
# Stage 4: Python dependencies
|
||||
FROM docker.io/python:3.12.1-slim-bookworm AS python-deps
|
||||
|
||||
WORKDIR /ak-root/poetry
|
||||
@ -107,7 +89,7 @@ RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
|
||||
pip3 install poetry && \
|
||||
poetry install --only=main --no-ansi --no-interaction
|
||||
|
||||
# Stage 6: Run
|
||||
# Stage 5: Run
|
||||
FROM docker.io/python:3.12.1-slim-bookworm AS final-image
|
||||
|
||||
ARG GIT_BUILD_HASH
|
||||
@ -149,7 +131,6 @@ COPY --from=go-builder /go/authentik /bin/authentik
|
||||
COPY --from=python-deps /ak-root/venv /ak-root/venv
|
||||
COPY --from=web-builder /work/web/dist/ /web/dist/
|
||||
COPY --from=web-builder /work/web/authentik/ /web/authentik/
|
||||
COPY --from=website-builder /work/website/help/ /website/help/
|
||||
COPY --from=geoip /usr/share/GeoIP /geoip
|
||||
|
||||
USER 1000
|
||||
|
@ -22,7 +22,6 @@ func (ws *WebServer) configureStatic() {
|
||||
distFs := http.FileServer(http.Dir("./web/dist"))
|
||||
distHandler := http.StripPrefix("/static/dist/", distFs)
|
||||
authentikHandler := http.StripPrefix("/static/authentik/", http.FileServer(http.Dir("./web/authentik")))
|
||||
helpHandler := http.FileServer(http.Dir("./website/help/"))
|
||||
indexLessRouter.PathPrefix("/static/dist/").Handler(distHandler)
|
||||
indexLessRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
|
||||
|
||||
@ -42,9 +41,6 @@ func (ws *WebServer) configureStatic() {
|
||||
|
||||
indexLessRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fs))
|
||||
|
||||
statRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", helpHandler))
|
||||
statRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently))
|
||||
|
||||
ws.lh.Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header()["Content-Type"] = []string{"text/plain"}
|
||||
rw.WriteHeader(200)
|
||||
|
6
poetry.lock
generated
6
poetry.lock
generated
@ -2543,13 +2543,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pdoc"
|
||||
version = "14.3.0"
|
||||
version = "14.4.0"
|
||||
description = "API Documentation for Python Projects"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pdoc-14.3.0-py3-none-any.whl", hash = "sha256:9a8f9a48bda5a99c249367c2b99779dbdd9f4a56f905068c9c2d6868dbae6882"},
|
||||
{file = "pdoc-14.3.0.tar.gz", hash = "sha256:40bf8f092fcd91560d5e6cebb7c21b65df699f90a468c8ea316235c3368d5449"},
|
||||
{file = "pdoc-14.4.0-py3-none-any.whl", hash = "sha256:6ea4fe07620b1f7601e2708a307a257636ec206e20b5611640b30f2e3cab47d6"},
|
||||
{file = "pdoc-14.4.0.tar.gz", hash = "sha256:c92edc425429ccbe287ace2a027953c24f13de53eab484c1a6d31ca72dd2fda9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
146
tests/wdio/package-lock.json
generated
146
tests/wdio/package-lock.json
generated
@ -9,15 +9,15 @@
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.19.0",
|
||||
"@wdio/cli": "^8.28.0",
|
||||
"@wdio/local-runner": "^8.28.0",
|
||||
"@wdio/mocha-framework": "^8.28.0",
|
||||
"@wdio/spec-reporter": "^8.28.0",
|
||||
"@wdio/cli": "^8.28.6",
|
||||
"@wdio/local-runner": "^8.28.7",
|
||||
"@wdio/mocha-framework": "^8.28.6",
|
||||
"@wdio/spec-reporter": "^8.28.6",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-sonarjs": "^0.23.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.2.3",
|
||||
"prettier": "^3.2.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3",
|
||||
"wdio-wait-for": "^3.0.10"
|
||||
@ -1166,18 +1166,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@wdio/cli": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.28.0.tgz",
|
||||
"integrity": "sha512-pC15cIh1N14R6qidiymSmKtXEvvJBGrrWqKi8vlVVfby81TQEpfD2VqymzqDqv+YsLIp2J4XD8rwwcSvqtA9UA==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.28.6.tgz",
|
||||
"integrity": "sha512-cBgm/RA12tlKGqIywsqAJaACST2tbcBtbkNl16io88iiBKAWOMlK5tX75+5dNEeyKs7JlQqBJ+3toXcypf68tA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.1",
|
||||
"@wdio/config": "8.28.0",
|
||||
"@wdio/globals": "8.28.0",
|
||||
"@wdio/config": "8.28.6",
|
||||
"@wdio/globals": "8.28.6",
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/protocols": "8.24.12",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/utils": "8.28.0",
|
||||
"@wdio/types": "8.28.6",
|
||||
"@wdio/utils": "8.28.6",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"chalk": "^5.2.0",
|
||||
"chokidar": "^3.5.3",
|
||||
@ -1192,7 +1192,7 @@
|
||||
"lodash.union": "^4.6.0",
|
||||
"read-pkg-up": "^10.0.0",
|
||||
"recursive-readdir": "^2.2.3",
|
||||
"webdriverio": "8.28.0",
|
||||
"webdriverio": "8.28.6",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
@ -1215,14 +1215,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/config": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.28.0.tgz",
|
||||
"integrity": "sha512-uXav11uUZSqbYyXGLzyggO8togdm6Bjdjkg8f0zZe4nQpqKpLAkcH7jRiekhuj7oIV5hZai6w5YFhFy5nsw/QA==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.28.6.tgz",
|
||||
"integrity": "sha512-rJ7GFnzg55MvG/CmN3rX79fFFBMcpoFZpILPTNaWJg43lBxidHue5pm7kXJT06D41sSJJPbtgoh6w4VPThIJrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/utils": "8.28.0",
|
||||
"@wdio/types": "8.28.6",
|
||||
"@wdio/utils": "8.28.6",
|
||||
"decamelize": "^6.0.0",
|
||||
"deepmerge-ts": "^5.0.0",
|
||||
"glob": "^10.2.2",
|
||||
@ -1233,29 +1233,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/globals": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.28.0.tgz",
|
||||
"integrity": "sha512-wVgkHOsKskZYu6FPaJpT19tYul3hi7nkB/TayYIk1rQTRuf3hoP2vHVjibGsU9W3JdhrZA/MUNTm5yrID70KZA==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.28.6.tgz",
|
||||
"integrity": "sha512-6Wjk7iWnpK1ft/caTAhDXxlmwU+pARouzCssqUtdO/vlvP1VhUo3xArCA2dzzJgE+uD16KLR63DRpgkTdwWPkQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^16.13 || >=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"expect-webdriverio": "^4.8.0",
|
||||
"webdriverio": "8.28.0"
|
||||
"webdriverio": "8.28.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/local-runner": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.28.0.tgz",
|
||||
"integrity": "sha512-zPev8IfItJtIWArTRyr9XjPu4Kp4kO0B/NAJmGQgDauLMBBzciwf35tPp1CFmggGtaO4czryAclyk2CsCkkAoA==",
|
||||
"version": "8.28.7",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.28.7.tgz",
|
||||
"integrity": "sha512-QOeJluWEV3My+41f4kHe7Bo08UcSd3TvH4TbPMflOkl0VvGqxJuKfXlllkQhJ8w5e53fBRsf368vzHscbX86/A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/repl": "8.24.12",
|
||||
"@wdio/runner": "8.28.0",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/runner": "8.28.7",
|
||||
"@wdio/types": "8.28.6",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"split2": "^4.1.0",
|
||||
"stream-buffers": "^3.0.2"
|
||||
@ -1292,16 +1292,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/mocha-framework": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.28.0.tgz",
|
||||
"integrity": "sha512-ykXbJXu2sb7agTnLP6Tv2Ak+ee3ZG3Tag2bT30j30yiulnNpIIuHMuSgrxDs2NKzlF2Q4qla/SrjiMPSoZYhsw==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.28.6.tgz",
|
||||
"integrity": "sha512-3jTI1GilFGZROJi+UCywEt/ac0b8ETQkG9YlkTAVcxMqG+wUKY2Ks7ow/UDGAaYMHkrEPdopvUFGZmJmpNMKUg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/utils": "8.28.0",
|
||||
"@wdio/types": "8.28.6",
|
||||
"@wdio/utils": "8.28.6",
|
||||
"mocha": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -1327,14 +1327,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/reporter": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.28.0.tgz",
|
||||
"integrity": "sha512-O2MfFv1xIm95cBnTgZINpiejVlG8LpiXrvlmj9FPPiGzcWIsERtbTHDb6+8UagQZv9nBLmISLARUyR7XRYfwLQ==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.28.6.tgz",
|
||||
"integrity": "sha512-2KUSytk75fjT4XGEL43u1XLzZSvu1SUg3a7h8fRm2fU2q1dCC16TJk5eod4eSxHzjm2fL1vwFQIAZkwO3WyQZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/types": "8.28.6",
|
||||
"diff": "^5.0.0",
|
||||
"object-inspect": "^1.12.0"
|
||||
},
|
||||
@ -1343,35 +1343,35 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/runner": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.28.0.tgz",
|
||||
"integrity": "sha512-IPaYSSjN6DDn75gDfzGQtFAu5oE1ee90L2xzXCYXK7xR4xr4O0IgNtFTq5cuLZsPRzJsGoq3z+1GxGSogC3u1A==",
|
||||
"version": "8.28.7",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.28.7.tgz",
|
||||
"integrity": "sha512-qk/3cMccCLXrIMN4a/vqmL0UeKaGXDclL/8buWC61IeZZ3SpDUgOoCJH47ULrkIeuoXXrnttZvo12fGjb8zmOg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/config": "8.28.0",
|
||||
"@wdio/globals": "8.28.0",
|
||||
"@wdio/config": "8.28.6",
|
||||
"@wdio/globals": "8.28.6",
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/utils": "8.28.0",
|
||||
"@wdio/types": "8.28.6",
|
||||
"@wdio/utils": "8.28.6",
|
||||
"deepmerge-ts": "^5.0.0",
|
||||
"expect-webdriverio": "^4.8.0",
|
||||
"gaze": "^1.1.2",
|
||||
"webdriver": "8.28.0",
|
||||
"webdriverio": "8.28.0"
|
||||
"webdriver": "8.28.6",
|
||||
"webdriverio": "8.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.13 || >=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/spec-reporter": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.28.0.tgz",
|
||||
"integrity": "sha512-ukyViS7KbeL7Q0+8gHqx1C4YZJT5ne1/6fi0dbgnGNPe+R7c76vSmf0lIGGiqEcDV1W27E1OoL17NwRKBIt0Pw==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.28.6.tgz",
|
||||
"integrity": "sha512-ZMwbM7hxcV0DumGaK0Y9jzPgTjwYHN539FFcEgbYZV1eNKUyIi9cdEP2xIWZiYQHmq2eKBMlUEPA87mwVZ1V6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@wdio/reporter": "8.28.0",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/reporter": "8.28.6",
|
||||
"@wdio/types": "8.28.6",
|
||||
"chalk": "^5.1.2",
|
||||
"easy-table": "^1.2.0",
|
||||
"pretty-ms": "^7.0.0"
|
||||
@ -1393,9 +1393,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/types": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.28.0.tgz",
|
||||
"integrity": "sha512-4/mUn3IGNa1GTiV0PMOtl1sRqStpbHOQldxz4Vheh0lYNc15W12jXRm84CwGsV6UW93GO9W2K9EprFJsUjc9sg==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.28.6.tgz",
|
||||
"integrity": "sha512-FU3mMRqULpc2XYh6DrSo/KgNoaS6EO9GFJQX5q7+EiOAqVeo1TCLggvAWIPayKyjfD1/ctd9q+uW9vmNicaOjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0"
|
||||
@ -1405,14 +1405,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@wdio/utils": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.28.0.tgz",
|
||||
"integrity": "sha512-v3xDJuQShLSfHW/Ee0y3z9ZtiV/UrILlucgKBCwCpLwHnO5HhfAH4Ehirt0yzQvYz+Pn9BuOXJImD/wsSbJtLw==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.28.6.tgz",
|
||||
"integrity": "sha512-EYQmGvejMiTMB18lW3CDc6cR+jXXjxDebP6ci53oK20QPx9VBuMQZdCbuoftKrKVLA+e9Fk0XfXq8xWYbjAvBQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "^1.6.0",
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/types": "8.28.6",
|
||||
"decamelize": "^6.0.0",
|
||||
"deepmerge-ts": "^5.1.0",
|
||||
"edgedriver": "^5.3.5",
|
||||
@ -6508,9 +6508,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.3.tgz",
|
||||
"integrity": "sha512-QNhUTBq+mqt1oH1dTfY3phOKNhcDdJkfttHI6u0kj7M2+c+7fmNKlgh2GhnHiqMcbxJ+a0j2igz/2jfl9QKLuw==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
|
||||
"integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
@ -8520,18 +8520,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webdriver": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.28.0.tgz",
|
||||
"integrity": "sha512-1ASMK+sNfVh5rdaRRk+eFLIfae93ViXHJBpuJemeORwZkfOJNF2CNSZl5uK2e6+nzbkY2cjM6QsZwfhL3lCiRg==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.28.6.tgz",
|
||||
"integrity": "sha512-qKZuG2uqGhq2xjk14vSAvE3C6TTTYQyOqDHQOSWNzPNhdBI99g2h4EUbmO3bc/5YaWRsVWWp+RB7jfQZUcE/MA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@wdio/config": "8.28.0",
|
||||
"@wdio/config": "8.28.6",
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/protocols": "8.24.12",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/utils": "8.28.0",
|
||||
"@wdio/types": "8.28.6",
|
||||
"@wdio/utils": "8.28.6",
|
||||
"deepmerge-ts": "^5.1.0",
|
||||
"got": "^12.6.1",
|
||||
"ky": "^0.33.0",
|
||||
@ -8542,18 +8542,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webdriverio": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.28.0.tgz",
|
||||
"integrity": "sha512-rRVE8pvcxAEqnhhC70oMFkUZ82YWbpXYyzKgfl2LKBue13AHaiN5qWncsJv29rqREIim0dNj6q2JuuUTDFm1gg==",
|
||||
"version": "8.28.6",
|
||||
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.28.6.tgz",
|
||||
"integrity": "sha512-vWo3Qx0bWubzUBBLaLYgeQkSN63KTfW5P3IbayhJ8Qx+NVVnclG0mu4mQj00gvj8+3a8opef6T54gdzitVSHSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^20.1.0",
|
||||
"@wdio/config": "8.28.0",
|
||||
"@wdio/config": "8.28.6",
|
||||
"@wdio/logger": "8.28.0",
|
||||
"@wdio/protocols": "8.24.12",
|
||||
"@wdio/repl": "8.24.12",
|
||||
"@wdio/types": "8.28.0",
|
||||
"@wdio/utils": "8.28.0",
|
||||
"@wdio/types": "8.28.6",
|
||||
"@wdio/utils": "8.28.6",
|
||||
"archiver": "^6.0.0",
|
||||
"aria-query": "^5.0.0",
|
||||
"css-shorthand-properties": "^1.1.1",
|
||||
@ -8570,7 +8570,7 @@
|
||||
"resq": "^1.9.1",
|
||||
"rgb2hex": "0.2.5",
|
||||
"serialize-error": "^11.0.1",
|
||||
"webdriver": "8.28.0"
|
||||
"webdriver": "8.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.13 || >=18"
|
||||
|
@ -6,15 +6,15 @@
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.19.0",
|
||||
"@wdio/cli": "^8.28.0",
|
||||
"@wdio/local-runner": "^8.28.0",
|
||||
"@wdio/mocha-framework": "^8.28.0",
|
||||
"@wdio/spec-reporter": "^8.28.0",
|
||||
"@wdio/cli": "^8.28.6",
|
||||
"@wdio/local-runner": "^8.28.7",
|
||||
"@wdio/mocha-framework": "^8.28.6",
|
||||
"@wdio/spec-reporter": "^8.28.6",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-sonarjs": "^0.23.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.2.3",
|
||||
"prettier": "^3.2.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3",
|
||||
"wdio-wait-for": "^3.0.10"
|
||||
|
668
web/package-lock.json
generated
668
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -87,14 +87,14 @@
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@spotlightjs/spotlight": "^1.2.6",
|
||||
"@storybook/addon-essentials": "^7.6.8",
|
||||
"@storybook/addon-links": "^7.6.8",
|
||||
"@storybook/api": "^7.6.8",
|
||||
"@spotlightjs/spotlight": "^1.2.7",
|
||||
"@storybook/addon-essentials": "^7.6.9",
|
||||
"@storybook/addon-links": "^7.6.9",
|
||||
"@storybook/api": "^7.6.9",
|
||||
"@storybook/blocks": "^7.6.4",
|
||||
"@storybook/manager-api": "^7.6.8",
|
||||
"@storybook/web-components": "^7.6.8",
|
||||
"@storybook/web-components-vite": "^7.6.8",
|
||||
"@storybook/manager-api": "^7.6.9",
|
||||
"@storybook/web-components": "^7.6.9",
|
||||
"@storybook/web-components-vite": "^7.6.9",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@types/chart.js": "^2.9.41",
|
||||
"@types/codemirror": "5.60.15",
|
||||
@ -123,7 +123,7 @@
|
||||
"rollup-plugin-cssimport": "^1.0.3",
|
||||
"rollup-plugin-modify": "^3.0.0",
|
||||
"rollup-plugin-postcss-lit": "^2.1.0",
|
||||
"storybook": "^7.6.8",
|
||||
"storybook": "^7.6.9",
|
||||
"storybook-addon-mock": "^4.3.0",
|
||||
"ts-lit-plugin": "^2.0.2",
|
||||
"tslib": "^2.6.2",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 717 KiB After Width: | Height: | Size: 699 KiB |
@ -134,9 +134,11 @@ html > form > input {
|
||||
);
|
||||
max-height: 9rem;
|
||||
}
|
||||
|
||||
.ak-brand {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
.ak-brand img {
|
||||
padding: 0 2rem;
|
||||
|
@ -1,324 +0,0 @@
|
||||
import { PreventFormSubmit } from "@goauthentik/app/elements/forms/helpers";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { ascii_letters, digits, groupBy, randomString } from "@goauthentik/common/utils";
|
||||
import { adaptCSS } from "@goauthentik/common/utils";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html, render } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFSelect from "@patternfly/patternfly/components/Select/select.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@customElement("ak-search-select")
|
||||
export class SearchSelect<T> extends CustomEmitterElement(AKElement) {
|
||||
@property()
|
||||
query?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
objects?: T[];
|
||||
|
||||
@property({ attribute: false })
|
||||
selectedObject?: T;
|
||||
|
||||
@property()
|
||||
name?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
open = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
blankable = false;
|
||||
|
||||
@property()
|
||||
placeholder: string = msg("Select an object.");
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFForm, PFFormControl, PFSelect];
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
fetchObjects!: (query?: string) => Promise<T[]>;
|
||||
|
||||
@property({ attribute: false })
|
||||
renderElement!: (element: T) => string;
|
||||
|
||||
@property({ attribute: false })
|
||||
renderDescription?: (element: T) => TemplateResult;
|
||||
|
||||
@property({ attribute: false })
|
||||
value!: (element: T | undefined) => unknown;
|
||||
|
||||
@property({ attribute: false })
|
||||
selected?: (element: T, elements: T[]) => boolean;
|
||||
|
||||
@property()
|
||||
emptyOption = "---------";
|
||||
|
||||
@property({ attribute: false })
|
||||
groupBy: (items: T[]) => [string, T[]][] = (items: T[]): [string, T[]][] => {
|
||||
return groupBy(items, () => {
|
||||
return "";
|
||||
});
|
||||
};
|
||||
|
||||
scrollHandler?: () => void;
|
||||
observer: IntersectionObserver;
|
||||
dropdownUID: string;
|
||||
dropdownContainer: HTMLDivElement;
|
||||
isFetchingData = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (!document.adoptedStyleSheets.includes(PFDropdown)) {
|
||||
document.adoptedStyleSheets = adaptCSS([...document.adoptedStyleSheets, PFDropdown]);
|
||||
}
|
||||
this.dropdownContainer = document.createElement("div");
|
||||
this.observer = new IntersectionObserver(() => {
|
||||
this.open = false;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll<HTMLInputElement>(
|
||||
".pf-c-form-control.pf-c-select__toggle-typeahead",
|
||||
)
|
||||
.forEach((input) => {
|
||||
input.blur();
|
||||
});
|
||||
});
|
||||
this.observer.observe(this);
|
||||
this.dropdownUID = `dropdown-${randomString(10, ascii_letters + digits)}`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
shouldUpdate(changedProperties: Map<string, any>) {
|
||||
if (changedProperties.has("selectedObject")) {
|
||||
this.dispatchCustomEvent("ak-change", {
|
||||
value: this.selectedObject,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
toForm(): unknown {
|
||||
if (!this.objects) {
|
||||
throw new PreventFormSubmit(msg("Loading options..."));
|
||||
}
|
||||
return this.value(this.selectedObject) || "";
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
this.updateData();
|
||||
}
|
||||
|
||||
updateData(): void {
|
||||
if (this.isFetchingData) {
|
||||
return;
|
||||
}
|
||||
this.isFetchingData = true;
|
||||
this.fetchObjects(this.query).then((objects) => {
|
||||
objects.forEach((obj) => {
|
||||
if (this.selected && this.selected(obj, objects || [])) {
|
||||
this.selectedObject = obj;
|
||||
}
|
||||
});
|
||||
this.objects = objects;
|
||||
this.isFetchingData = false;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.dropdownContainer = document.createElement("div");
|
||||
this.dropdownContainer.dataset["managedBy"] = "ak-search-select";
|
||||
if (this.name) {
|
||||
this.dropdownContainer.dataset["managedFor"] = this.name;
|
||||
}
|
||||
document.body.append(this.dropdownContainer);
|
||||
this.updateData();
|
||||
this.addEventListener(EVENT_REFRESH, this.updateData);
|
||||
this.scrollHandler = () => {
|
||||
this.requestUpdate();
|
||||
};
|
||||
window.addEventListener("scroll", this.scrollHandler);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener(EVENT_REFRESH, this.updateData);
|
||||
if (this.scrollHandler) {
|
||||
window.removeEventListener("scroll", this.scrollHandler);
|
||||
}
|
||||
this.dropdownContainer.remove();
|
||||
this.observer.disconnect();
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a little bit hacky. Because we mainly want to use this field in modal-based forms,
|
||||
* rendering this menu inline makes the menu not overlay over top of the modal, and cause
|
||||
* the modal to scroll.
|
||||
* Hence, we render the menu into the document root, hide it when this menu isn't open
|
||||
* and remove it on disconnect
|
||||
* Also to move it to the correct position we're getting this elements's position and use that
|
||||
* to position the menu
|
||||
* The other downside this has is that, since we're rendering outside of a shadow root,
|
||||
* the pf-c-dropdown CSS needs to be loaded on the body.
|
||||
*/
|
||||
renderMenu(): void {
|
||||
if (!this.objects) {
|
||||
return;
|
||||
}
|
||||
const pos = this.getBoundingClientRect();
|
||||
let groupedItems = this.groupBy(this.objects);
|
||||
let shouldRenderGroups = true;
|
||||
if (groupedItems.length === 1) {
|
||||
if (groupedItems[0].length < 1 || groupedItems[0][0] === "") {
|
||||
shouldRenderGroups = false;
|
||||
}
|
||||
}
|
||||
if (groupedItems.length === 0) {
|
||||
shouldRenderGroups = false;
|
||||
groupedItems = [["", []]];
|
||||
}
|
||||
const renderGroup = (items: T[], tabIndexStart: number): TemplateResult => {
|
||||
return html`${items.map((obj, index) => {
|
||||
let desc = undefined;
|
||||
if (this.renderDescription) {
|
||||
desc = this.renderDescription(obj);
|
||||
}
|
||||
return html`
|
||||
<li>
|
||||
<button
|
||||
class="pf-c-dropdown__menu-item ${desc === undefined
|
||||
? ""
|
||||
: "pf-m-description"}"
|
||||
role="option"
|
||||
@click=${() => {
|
||||
this.selectedObject = obj;
|
||||
this.open = false;
|
||||
}}
|
||||
tabindex=${index + tabIndexStart}
|
||||
>
|
||||
${desc === undefined
|
||||
? this.renderElement(obj)
|
||||
: html`
|
||||
<div class="pf-c-dropdown__menu-item-main">
|
||||
${this.renderElement(obj)}
|
||||
</div>
|
||||
<div class="pf-c-dropdown__menu-item-description">
|
||||
${desc}
|
||||
</div>
|
||||
`}
|
||||
</button>
|
||||
</li>
|
||||
`;
|
||||
})}`;
|
||||
};
|
||||
render(
|
||||
html`<div
|
||||
class="pf-c-dropdown pf-m-expanded"
|
||||
style="position: fixed; inset: 0px auto auto 0px; z-index: 9999; transform: translate(${pos.x}px, ${pos.y +
|
||||
this.offsetHeight}px); width: ${pos.width}px; ${this.open
|
||||
? ""
|
||||
: "visibility: hidden;"}"
|
||||
>
|
||||
<ul
|
||||
class="pf-c-dropdown__menu pf-m-static"
|
||||
role="listbox"
|
||||
style="max-height:50vh;overflow-y:auto;"
|
||||
id=${this.dropdownUID}
|
||||
tabindex="0"
|
||||
>
|
||||
${this.blankable
|
||||
? html`
|
||||
<li>
|
||||
<button
|
||||
class="pf-c-dropdown__menu-item"
|
||||
role="option"
|
||||
@click=${() => {
|
||||
this.selectedObject = undefined;
|
||||
this.open = false;
|
||||
}}
|
||||
tabindex="0"
|
||||
>
|
||||
${this.emptyOption}
|
||||
</button>
|
||||
</li>
|
||||
`
|
||||
: html``}
|
||||
${shouldRenderGroups
|
||||
? html`${groupedItems.map(([group, items], idx) => {
|
||||
return html`
|
||||
<section class="pf-c-dropdown__group">
|
||||
<h1 class="pf-c-dropdown__group-title">${group}</h1>
|
||||
<ul>
|
||||
${renderGroup(items, idx)}
|
||||
</ul>
|
||||
</section>
|
||||
`;
|
||||
})}`
|
||||
: html`${renderGroup(groupedItems[0][1], 0)}`}
|
||||
</ul>
|
||||
</div>`,
|
||||
this.dropdownContainer,
|
||||
{ host: this },
|
||||
);
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
this.renderMenu();
|
||||
let value = "";
|
||||
if (!this.objects) {
|
||||
value = msg("Loading...");
|
||||
} else if (this.selectedObject) {
|
||||
value = this.renderElement(this.selectedObject);
|
||||
} else if (this.blankable) {
|
||||
value = this.emptyOption;
|
||||
}
|
||||
return html`<div class="pf-c-select">
|
||||
<div class="pf-c-select__toggle pf-m-typeahead">
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<input
|
||||
class="pf-c-form-control pf-c-select__toggle-typeahead"
|
||||
type="text"
|
||||
placeholder=${this.placeholder}
|
||||
spellcheck="false"
|
||||
@input=${(ev: InputEvent) => {
|
||||
this.query = (ev.target as HTMLInputElement).value;
|
||||
this.updateData();
|
||||
}}
|
||||
@focus=${() => {
|
||||
this.open = true;
|
||||
this.renderMenu();
|
||||
}}
|
||||
@blur=${(ev: FocusEvent) => {
|
||||
// For Safari, we get the <ul> element itself here when clicking on one of
|
||||
// it's buttons, as the container has tabindex set
|
||||
if (
|
||||
ev.relatedTarget &&
|
||||
(ev.relatedTarget as HTMLElement).id === this.dropdownUID
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Check if we're losing focus to one of our dropdown items, and if such don't blur
|
||||
if (ev.relatedTarget instanceof HTMLButtonElement) {
|
||||
const parentMenu = ev.relatedTarget.closest(
|
||||
"ul.pf-c-dropdown__menu.pf-m-static",
|
||||
);
|
||||
if (parentMenu && parentMenu.id === this.dropdownUID) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.open = false;
|
||||
this.renderMenu();
|
||||
}}
|
||||
.value=${value}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
370
web/src/elements/forms/SearchSelect/ak-search-select.ts
Normal file
370
web/src/elements/forms/SearchSelect/ak-search-select.ts
Normal file
@ -0,0 +1,370 @@
|
||||
import { PreventFormSubmit } from "@goauthentik/app/elements/forms/helpers";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { ascii_letters, digits, groupBy, randomString } from "@goauthentik/common/utils";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, render } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
|
||||
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFSelect from "@patternfly/patternfly/components/Select/select.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
type Group<T> = [string, T[]];
|
||||
|
||||
@customElement("ak-search-select")
|
||||
export class SearchSelect<T> extends CustomEmitterElement(AKElement) {
|
||||
// A function which takes the query state object (accepting that it may be empty) and returns a
|
||||
// new collection of objects.
|
||||
@property({ attribute: false })
|
||||
fetchObjects!: (query?: string) => Promise<T[]>;
|
||||
|
||||
// A function passed to this object that extracts a string representation of items of the
|
||||
// collection under search.
|
||||
@property({ attribute: false })
|
||||
renderElement!: (element: T) => string;
|
||||
|
||||
// A function passed to this object that extracts an HTML representation of additional
|
||||
// information for items of the collection under search.
|
||||
@property({ attribute: false })
|
||||
renderDescription?: (element: T) => TemplateResult;
|
||||
|
||||
// A function which returns the currently selected object's primary key, used for serialization
|
||||
// into forms.
|
||||
@property({ attribute: false })
|
||||
value!: (element: T | undefined) => unknown;
|
||||
|
||||
// A function passed to this object that determines an object in the collection under search
|
||||
// should be automatically selected. Only used when the search itself is responsible for
|
||||
// fetching the data; sets an initial default value.
|
||||
@property({ attribute: false })
|
||||
selected?: (element: T, elements: T[]) => boolean;
|
||||
|
||||
// A function passed to this object (or using the default below) that groups objects in the
|
||||
// collection under search into categories.
|
||||
@property({ attribute: false })
|
||||
groupBy: (items: T[]) => [string, T[]][] = (items: T[]): [string, T[]][] => {
|
||||
return groupBy(items, () => {
|
||||
return "";
|
||||
});
|
||||
};
|
||||
|
||||
// Whether or not the dropdown component can be left blank
|
||||
@property({ type: Boolean })
|
||||
blankable = false;
|
||||
|
||||
// An initial string to filter the search contents, and the value of the input which further
|
||||
// serves to restrict the search
|
||||
@property()
|
||||
query?: string;
|
||||
|
||||
// The objects currently available under search
|
||||
@property({ attribute: false })
|
||||
objects?: T[];
|
||||
|
||||
// The currently selected object
|
||||
@property({ attribute: false })
|
||||
selectedObject?: T;
|
||||
|
||||
// Not used in this object. No known purpose.
|
||||
@property()
|
||||
name?: string;
|
||||
|
||||
// Whether or not the dropdown component is visible.
|
||||
@property({ type: Boolean })
|
||||
open = false;
|
||||
|
||||
// The textual placeholder for the search's <input> object, if currently empty. Used as the
|
||||
// native <input> object's `placeholder` field.
|
||||
@property()
|
||||
placeholder: string = msg("Select an object.");
|
||||
|
||||
// A textual string representing "The user has affirmed they want to leave the selection blank."
|
||||
// Only used if `blankable` above is true.
|
||||
@property()
|
||||
emptyOption = "---------";
|
||||
|
||||
// Handle the behavior of the drop-down when the :host scrolls off the page.
|
||||
scrollHandler?: () => void;
|
||||
observer: IntersectionObserver;
|
||||
|
||||
// Handle communication between the :host and the portal
|
||||
dropdownUID: string;
|
||||
dropdownContainer: HTMLDivElement;
|
||||
|
||||
isFetchingData = false;
|
||||
|
||||
static styles = [PFBase, PFForm, PFFormControl, PFSelect];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (!document.adoptedStyleSheets.includes(PFDropdown)) {
|
||||
document.adoptedStyleSheets = [...document.adoptedStyleSheets, PFDropdown];
|
||||
}
|
||||
this.dropdownContainer = document.createElement("div");
|
||||
this.observer = new IntersectionObserver(() => {
|
||||
this.open = false;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll<HTMLInputElement>(
|
||||
".pf-c-form-control.pf-c-select__toggle-typeahead",
|
||||
)
|
||||
.forEach((input) => {
|
||||
input.blur();
|
||||
});
|
||||
});
|
||||
this.observer.observe(this);
|
||||
this.dropdownUID = `dropdown-${randomString(10, ascii_letters + digits)}`;
|
||||
this.onMenuItemClick = this.onMenuItemClick.bind(this);
|
||||
this.renderWithMenuGroupTitle = this.renderWithMenuGroupTitle.bind(this);
|
||||
}
|
||||
|
||||
toForm(): unknown {
|
||||
if (!this.objects) {
|
||||
throw new PreventFormSubmit(msg("Loading options..."));
|
||||
}
|
||||
return this.value(this.selectedObject) || "";
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
this.updateData();
|
||||
}
|
||||
|
||||
updateData(): void {
|
||||
if (this.isFetchingData) {
|
||||
return;
|
||||
}
|
||||
this.isFetchingData = true;
|
||||
this.fetchObjects(this.query).then((objects) => {
|
||||
objects.forEach((obj) => {
|
||||
if (this.selected && this.selected(obj, objects || [])) {
|
||||
this.selectedObject = obj;
|
||||
}
|
||||
});
|
||||
this.objects = objects;
|
||||
this.isFetchingData = false;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.dropdownContainer = document.createElement("div");
|
||||
this.dropdownContainer.dataset["managedBy"] = "ak-search-select";
|
||||
document.body.append(this.dropdownContainer);
|
||||
this.updateData();
|
||||
this.addEventListener(EVENT_REFRESH, this.updateData);
|
||||
this.scrollHandler = () => {
|
||||
this.requestUpdate();
|
||||
};
|
||||
window.addEventListener("scroll", this.scrollHandler);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener(EVENT_REFRESH, this.updateData);
|
||||
if (this.scrollHandler) {
|
||||
window.removeEventListener("scroll", this.scrollHandler);
|
||||
}
|
||||
this.dropdownContainer.remove();
|
||||
this.observer.disconnect();
|
||||
}
|
||||
|
||||
renderMenuItemWithDescription(obj: T, desc: TemplateResult, index: number) {
|
||||
return html`
|
||||
<li>
|
||||
<button
|
||||
class="pf-c-dropdown__menu-item pf-m-description"
|
||||
role="option"
|
||||
@click=${this.onMenuItemClick(obj)}
|
||||
tabindex=${index}
|
||||
>
|
||||
<div class="pf-c-dropdown__menu-item-main">${this.renderElement(obj)}</div>
|
||||
<div class="pf-c-dropdown__menu-item-description">${desc}</div>
|
||||
</button>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
renderMenuItemWithoutDescription(obj: T, index: number) {
|
||||
return html`
|
||||
<li>
|
||||
<button
|
||||
class="pf-c-dropdown__menu-item"
|
||||
role="option"
|
||||
@click=${this.onMenuItemClick(obj)}
|
||||
tabindex=${index}
|
||||
>
|
||||
${this.renderElement(obj)}
|
||||
</button>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
renderEmptyMenuItem() {
|
||||
return html`<li>
|
||||
<button
|
||||
class="pf-c-dropdown__menu-item"
|
||||
role="option"
|
||||
@click=${this.onMenuItemClick(undefined)}
|
||||
tabindex="0"
|
||||
>
|
||||
${this.emptyOption}
|
||||
</button>
|
||||
</li>`;
|
||||
}
|
||||
|
||||
onMenuItemClick(obj: T | undefined) {
|
||||
return () => {
|
||||
this.selectedObject = obj;
|
||||
this.open = false;
|
||||
};
|
||||
}
|
||||
|
||||
renderMenuGroup(items: T[], tabIndexStart: number) {
|
||||
const renderedItems = items.map((obj, index) => {
|
||||
const desc = this.renderDescription ? this.renderDescription(obj) : null;
|
||||
const tabIndex = index + tabIndexStart;
|
||||
return desc
|
||||
? this.renderMenuItemWithDescription(obj, desc, tabIndex)
|
||||
: this.renderMenuItemWithoutDescription(obj, tabIndex);
|
||||
});
|
||||
return html`${renderedItems}`;
|
||||
}
|
||||
|
||||
renderWithMenuGroupTitle([group, items]: Group<T>, idx: number) {
|
||||
return html`
|
||||
<section class="pf-c-dropdown__group">
|
||||
<h1 class="pf-c-dropdown__group-title">${group}</h1>
|
||||
<ul>
|
||||
${this.renderMenuGroup(items, idx)}
|
||||
</ul>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
get groupedItems(): [boolean, Group<T>[]] {
|
||||
const items = this.groupBy(this.objects || []);
|
||||
if (items.length === 0) {
|
||||
return [false, [["", []]]];
|
||||
}
|
||||
if (items.length === 1 && (items[0].length < 1 || items[0][0] === "")) {
|
||||
return [false, items];
|
||||
}
|
||||
return [true, items];
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a little bit hacky. Because we mainly want to use this field in modal-based forms,
|
||||
* rendering this menu inline makes the menu not overlay over top of the modal, and cause
|
||||
* the modal to scroll.
|
||||
* Hence, we render the menu into the document root, hide it when this menu isn't open
|
||||
* and remove it on disconnect
|
||||
* Also to move it to the correct position we're getting this elements's position and use that
|
||||
* to position the menu
|
||||
* The other downside this has is that, since we're rendering outside of a shadow root,
|
||||
* the pf-c-dropdown CSS needs to be loaded on the body.
|
||||
*/
|
||||
|
||||
renderMenu(): void {
|
||||
if (!this.objects) {
|
||||
return;
|
||||
}
|
||||
const [shouldRenderGroups, groupedItems] = this.groupedItems;
|
||||
|
||||
const pos = this.getBoundingClientRect();
|
||||
const position = {
|
||||
"position": "fixed",
|
||||
"inset": "0px auto auto 0px",
|
||||
"z-index": "9999",
|
||||
"transform": `translate(${pos.x}px, ${pos.y + this.offsetHeight}px)`,
|
||||
"width": `${pos.width}px`,
|
||||
...(this.open ? {} : { visibility: "hidden" }),
|
||||
};
|
||||
|
||||
render(
|
||||
html`<div style=${styleMap(position)} class="pf-c-dropdown pf-m-expanded">
|
||||
<ul
|
||||
class="pf-c-dropdown__menu pf-m-static"
|
||||
role="listbox"
|
||||
style="max-height:50vh;overflow-y:auto;"
|
||||
id=${this.dropdownUID}
|
||||
tabindex="0"
|
||||
>
|
||||
${this.blankable ? this.renderEmptyMenuItem() : html``}
|
||||
${shouldRenderGroups
|
||||
? html`${groupedItems.map(this.renderWithMenuGroupTitle)}`
|
||||
: html`${this.renderMenuGroup(groupedItems[0][1], 0)}`}
|
||||
</ul>
|
||||
</div>`,
|
||||
this.dropdownContainer,
|
||||
{ host: this },
|
||||
);
|
||||
}
|
||||
|
||||
get renderedValue() {
|
||||
// prettier-ignore
|
||||
return (!this.objects) ? msg("Loading...")
|
||||
: (this.selectedObject) ? this.renderElement(this.selectedObject)
|
||||
: (this.blankable) ? this.emptyOption
|
||||
: "";
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
this.renderMenu();
|
||||
|
||||
const onFocus = (ev: FocusEvent) => {
|
||||
this.open = true;
|
||||
this.renderMenu();
|
||||
if (this.blankable && this.renderedValue === this.emptyOption) {
|
||||
if (ev.target && ev.target instanceof HTMLInputElement) {
|
||||
ev.target.value = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onInput = (ev: InputEvent) => {
|
||||
this.query = (ev.target as HTMLInputElement).value;
|
||||
this.updateData();
|
||||
};
|
||||
|
||||
const onBlur = (ev: FocusEvent) => {
|
||||
// For Safari, we get the <ul> element itself here when clicking on one of
|
||||
// it's buttons, as the container has tabindex set
|
||||
if (ev.relatedTarget && (ev.relatedTarget as HTMLElement).id === this.dropdownUID) {
|
||||
return;
|
||||
}
|
||||
// Check if we're losing focus to one of our dropdown items, and if such don't blur
|
||||
if (ev.relatedTarget instanceof HTMLButtonElement) {
|
||||
const parentMenu = ev.relatedTarget.closest("ul.pf-c-dropdown__menu.pf-m-static");
|
||||
if (parentMenu && parentMenu.id === this.dropdownUID) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.open = false;
|
||||
this.renderMenu();
|
||||
};
|
||||
|
||||
return html`<div class="pf-c-select">
|
||||
<div class="pf-c-select__toggle pf-m-typeahead">
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<input
|
||||
class="pf-c-form-control pf-c-select__toggle-typeahead"
|
||||
type="text"
|
||||
placeholder=${this.placeholder}
|
||||
spellcheck="false"
|
||||
@input=${onInput}
|
||||
@focus=${onFocus}
|
||||
@blur=${onBlur}
|
||||
.value=${this.renderedValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default SearchSelect;
|
4
web/src/elements/forms/SearchSelect/index.ts
Normal file
4
web/src/elements/forms/SearchSelect/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { SearchSelect } from "./ak-search-select";
|
||||
|
||||
export { SearchSelect };
|
||||
export default SearchSelect;
|
@ -43,7 +43,6 @@ export class SidebarBrand extends WithTenantConfig(AKElement) {
|
||||
min-height: 114px;
|
||||
}
|
||||
.pf-c-brand img {
|
||||
width: 100%;
|
||||
padding: 0 0.5rem;
|
||||
height: 42px;
|
||||
}
|
||||
|
@ -505,7 +505,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
? html`
|
||||
<li>
|
||||
<a
|
||||
href="https://unsplash.com/@federize"
|
||||
href="https://unsplash.com/@theforestbirds"
|
||||
>${msg("Background image")}</a
|
||||
>
|
||||
</li>
|
||||
|
@ -6249,6 +6249,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -6525,6 +6525,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -6165,6 +6165,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -8217,6 +8217,14 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
<target>Nombre maximum de connections concurrentes à ce point de terminaison. Peut être défini à -1 pour désactiver la limite.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
<target>Coréen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
<target>Néerlandais</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
182
web/xliff/ko.xlf
182
web/xliff/ko.xlf
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<file target-language="ko" source-language="en" original="lit-localize-inputs" datatype="plaintext">
|
||||
<body>
|
||||
<trans-unit id="s4caed5b7a7e5d89b">
|
||||
@ -612,8 +612,8 @@
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="saa0e2675da69651b">
|
||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||
<target>URL "<x id="0" equiv-text="${this.url}"/>" 을 찾을 수 없습니다.</target>
|
||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||
<target>URL "<x id="0" equiv-text="${this.url}"/>" 을 찾을 수 없습니다.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s58cd9c2fe836d9c6">
|
||||
@ -1054,8 +1054,8 @@
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa8384c9c26731f83">
|
||||
<source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
|
||||
<target>리디렉션 URI를 허용하려면 이 값을 ".*"로 설정합니다. 이로 인해 발생할 수 있는 보안상의 영향에 유의하세요.</target>
|
||||
<source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
|
||||
<target>리디렉션 URI를 허용하려면 이 값을 ".*"로 설정합니다. 이로 인해 발생할 수 있는 보안상의 영향에 유의하세요.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s55787f4dfcdce52b">
|
||||
@ -1792,8 +1792,8 @@
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa90b7809586c35ce">
|
||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||
<target>전체 URL, 상대 경로를 입력하거나, 또는 'fa://fa-test'를 사용하여 Font Awesome 아이콘 "fa-test"를 사용합니다.</target>
|
||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||
<target>전체 URL, 상대 경로를 입력하거나, 또는 'fa://fa-test'를 사용하여 Font Awesome 아이콘 "fa-test"를 사용합니다.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s0410779cb47de312">
|
||||
@ -2972,7 +2972,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s76768bebabb7d543">
|
||||
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
|
||||
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
|
||||
<target>그룹 구성원이 포함된 필드입니다. 'memberUid' 필드를 사용하는 경우 값에 상대적인 고유 이름이 포함된 것으로 가정합니다 (예:'memberUid=some-user' 대신 'memberUid=cn=some-user,ou=groups,...').</target>
|
||||
|
||||
</trans-unit>
|
||||
@ -3764,8 +3764,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s7b1fba26d245cb1c">
|
||||
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
||||
<target>아카이브에 외부 로깅 솔루션을 사용하는 경우, 이 값을 "minutes=5"로 설정할 수 있습니다.</target>
|
||||
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
||||
<target>아카이브에 외부 로깅 솔루션을 사용하는 경우, 이 값을 "minutes=5"로 설정할 수 있습니다.</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s44536d20bb5c8257">
|
||||
@ -3774,8 +3774,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s3bb51cabb02b997e">
|
||||
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
|
||||
<target>서식: "weeks=3;days=2;hours=3,seconds=2".</target>
|
||||
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
|
||||
<target>서식: "weeks=3;days=2;hours=3,seconds=2".</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s04bfd02201db5ab8">
|
||||
@ -3967,8 +3967,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa95a538bfbb86111">
|
||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||
<target>정말 <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>" 을(를) 업데이트 하시겠습니까?</target>
|
||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||
<target>정말 <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>" 을(를) 업데이트 하시겠습니까?</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sc92d7cfb6ee1fec6">
|
||||
@ -5052,8 +5052,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sdf1d8edef27236f0">
|
||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||
<target>YubiKey 같은 "로밍" 인증기</target>
|
||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||
<target>YubiKey 같은 "로밍" 인증기</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sfffba7b23d8fb40c">
|
||||
@ -5387,8 +5387,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s2d5f69929bb7221d">
|
||||
<source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
|
||||
<target><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</target>
|
||||
<source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
|
||||
<target><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s3b7b519444181264">
|
||||
@ -5436,7 +5436,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s1608b2f94fa0dbd4">
|
||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||
<target>기간을 0 이상으로 설정하면, 사용자에게 '로그인 상태 유지'를 선택할 수 있는 옵션이 제공되며, 이 경우 세션이 여기에 지정된 시간만큼 연장됩니다.</target>
|
||||
|
||||
</trans-unit>
|
||||
@ -7926,8 +7926,8 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>사용자 생성과 <x id="0" equiv-text="${this.group.name}"/> 그룹 추가에 성공했습니다.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s824e0943a7104668">
|
||||
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
|
||||
<target>이 사용자는 "<x id="0" equiv-text="${this.targetGroup.name}"/>" 그룹에 추가됩니다.</target>
|
||||
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
|
||||
<target>이 사용자는 "<x id="0" equiv-text="${this.targetGroup.name}"/>" 그룹에 추가됩니다.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62e7f6ed7d9cb3ca">
|
||||
<source>Pretend user exists</source>
|
||||
@ -8004,7 +8004,145 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s047a5f0211fedc72">
|
||||
<source>Require Outpost (flow can only be executed from an outpost).</source>
|
||||
<target>Outpost필요 (플로우는 Outpost에서만 실행할 수 있음).</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="scc7f34824150bfb8">
|
||||
<source>Provider require enterprise.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s31f1afc1bfe1cb3a">
|
||||
<source>Learn more</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa2ea0fcd3ffa80e0">
|
||||
<source>Connection expiry</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6dd297c217729828">
|
||||
<source>Determines how long a session lasts before being disconnected and requiring re-authorization.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3271da6c18c25b18">
|
||||
<source>Connection settings.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2f4ca2148183d692">
|
||||
<source>Successfully updated endpoint.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5adee855dbe191d9">
|
||||
<source>Successfully created endpoint.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s61e136c0658e27d5">
|
||||
<source>Protocol</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa062b019ff0c8809">
|
||||
<source>RDP</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s97f9bf19fa5b57d1">
|
||||
<source>SSH</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7c100119e9ffcc32">
|
||||
<source>VNC</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6b05f9d8801fc14f">
|
||||
<source>Host</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb474f652a2c2fc76">
|
||||
<source>Hostname/IP to connect to.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sc39f6abf0daedb0f">
|
||||
<source>Maximum concurrent connections</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8276649077e8715c">
|
||||
<source>Endpoint(s)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf1dabfe0fe8a75ad">
|
||||
<source>Update Endpoint</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s008496c7716b9812">
|
||||
<source>These bindings control which users will have access to this endpoint. Users must also have access to the application.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s38e7cd1a24e70faa">
|
||||
<source>Create Endpoint</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4770c10e5b1c028c">
|
||||
<source>RAC is in preview.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s168565f5ac74a89f">
|
||||
<source>Update RAC Provider</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s8465a2caa2d9ea5d">
|
||||
<source>Endpoints</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9857d883d8eb98fc">
|
||||
<source>General settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sd2066881798a1b96">
|
||||
<source>RDP settings</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb864dc36a463a155">
|
||||
<source>Ignore server certificate</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s20366a8d1eaaca54">
|
||||
<source>Enable wallpaper</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s1e44c5350ef7598c">
|
||||
<source>Enable font-smoothing</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s04ff5d6ae711e6d6">
|
||||
<source>Enable full window dragging</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s663ccbfdf27e8dd0">
|
||||
<source>Network binding</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb108a06693c67753">
|
||||
<source>No binding</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s5aab90c74f1233b8">
|
||||
<source>Bind ASN</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s488303b048afe83b">
|
||||
<source>Bind ASN and Network</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3268dcfe0c8234dc">
|
||||
<source>Bind ASN, Network and IP</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s226381aca231644f">
|
||||
<source>Configure if sessions created by this stage should be bound to the Networks they were created in.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2555a1f20f3fd93e">
|
||||
<source>GeoIP binding</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3d63c78f93c9a92e">
|
||||
<source>Bind Continent</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s395d5863b3a259b5">
|
||||
<source>Bind Continent and Country</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s625ea0c32b4b136c">
|
||||
<source>Bind Continent, Country and City</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4bc7a1a88961be90">
|
||||
<source>Configure if sessions created by this stage should be bound to their GeoIP-based location</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa06cd519ff151b6d">
|
||||
<source>RAC</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s28b99b59541f54ca">
|
||||
<source>Connection failed after <x id="0" equiv-text="${this.connectionAttempt}"/> attempts.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7c7d956418e1c8c8">
|
||||
<source>Re-connecting in <x id="0" equiv-text="${Math.max(1, delay / 1000)}"/> second(s).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sfc003381f593d943">
|
||||
<source>Connecting...</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s31aa94a0b3c7edb2">
|
||||
<source>Select endpoint to connect to</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
|
899
web/xliff/nl.xlf
899
web/xliff/nl.xlf
File diff suppressed because it is too large
Load Diff
@ -6373,6 +6373,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -8111,4 +8111,10 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
</body></file></xliff>
|
||||
|
@ -6158,6 +6158,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
5068
web/xliff/zh-CN.xlf
Normal file
5068
web/xliff/zh-CN.xlf
Normal file
File diff suppressed because it is too large
Load Diff
@ -8219,6 +8219,14 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
<target>允许到此端点的最大并发连接数。可以设置为 -1 以禁用限制。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
<target>韩语</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
<target>荷兰语</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -6206,6 +6206,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -8219,6 +8219,14 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
<target>允许到此端点的最大并发连接数。可以设置为 -1 以禁用限制。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
<target>韩语</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
<target>荷兰语</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -8095,6 +8095,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62418cbcd2a25498">
|
||||
<source>Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s94d61907ee22a8c1">
|
||||
<source>Korean</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s95d56e58f816d211">
|
||||
<source>Dutch</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -4,16 +4,16 @@ title: Docker Compose installation
|
||||
|
||||
This installation method is for test-setups and small-scale production setups.
|
||||
|
||||
:::info
|
||||
You can also [view a video walk-through](https://www.youtube.com/watch?v=O1qUbrk4Yc8) of the installation process on Docker (with bonus details about email configuration and other important options).
|
||||
:::
|
||||
|
||||
## Requirements
|
||||
|
||||
- A host with at least 2 CPU cores and 2 GB of RAM
|
||||
- Docker
|
||||
- Docker Compose
|
||||
|
||||
## Video
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/O1qUbrk4Yc8?si=HiSBjmJYhE_oJhB1&start=22" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
## Preparation
|
||||
|
||||
To download the latest `docker-compose.yml` open your terminal and navigate to the directory of your choice.
|
||||
|
@ -4,6 +4,6 @@ title: Installation
|
||||
|
||||
Everything you need to get authentik up and running! For information about upgrading to a new version, refer to the <b>Upgrade</b> section in the relevant [Release Notes](../releases).
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
<DocCardList />
|
@ -13,6 +13,10 @@ You can also [view a video walk-through](https://www.youtube.com/watch?v=O1qUbrk
|
||||
- Kubernetes
|
||||
- Helm
|
||||
|
||||
## Video
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/O1qUbrk4Yc8?si=hs-ZhbVk4Y-TW_Vw&start=562" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
### Generate Passwords
|
||||
|
||||
Start by generating passwords for the database and cache. You can use either of the following commands:
|
||||
|
@ -1,129 +0,0 @@
|
||||
const config = require("./docusaurus.config");
|
||||
import type { Config } from "@docusaurus/types";
|
||||
|
||||
module.exports = async function (): Promise<Config> {
|
||||
const remarkGithub = (await import("remark-github")).default;
|
||||
const defaultBuildUrl = (await import("remark-github")).defaultBuildUrl;
|
||||
const mainConfig = await config();
|
||||
return {
|
||||
title: "authentik",
|
||||
tagline: "Making authentication simple.",
|
||||
url: "https://goauthentik.io",
|
||||
baseUrl: "/if/help/",
|
||||
onBrokenLinks: "throw",
|
||||
favicon: "img/icon.png",
|
||||
organizationName: "BeryJu",
|
||||
projectName: "authentik",
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
logo: {
|
||||
alt: "authentik logo",
|
||||
src: "img/icon_left_brand.svg",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
to: "docs/",
|
||||
activeBasePath: "docs",
|
||||
label: "Docs",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
to: "integrations/",
|
||||
activeBasePath: "integrations",
|
||||
label: "Integrations",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
to: "developer-docs/",
|
||||
activeBasePath: "developer-docs",
|
||||
label: "Developer Docs",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
href: "https://github.com/goauthentik/authentik",
|
||||
label: "GitHub",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
href: "https://goauthentik.io/discord",
|
||||
label: "Discord",
|
||||
position: "right",
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
links: [],
|
||||
copyright: mainConfig.themeConfig.footer.copyright,
|
||||
},
|
||||
colorMode: mainConfig.themeConfig.colorMode,
|
||||
tableOfContents: mainConfig.themeConfig.tableOfContents,
|
||||
prims: mainConfig.themeConfig.prism,
|
||||
},
|
||||
presets: [
|
||||
[
|
||||
"@docusaurus/preset-classic",
|
||||
{
|
||||
docs: {
|
||||
id: "docs",
|
||||
sidebarPath: require.resolve("./sidebars.js"),
|
||||
editUrl:
|
||||
"https://github.com/goauthentik/authentik/edit/main/website/",
|
||||
remarkPlugins: [
|
||||
[
|
||||
remarkGithub,
|
||||
{
|
||||
repository: "goauthentik/authentik",
|
||||
// Only replace issues and PR links
|
||||
buildUrl: function (values) {
|
||||
return values.type === "issue"
|
||||
? defaultBuildUrl(values)
|
||||
: false;
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
pages: false,
|
||||
theme: {
|
||||
customCss: require.resolve("./src/css/custom.css"),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
"@docusaurus/plugin-content-docs",
|
||||
{
|
||||
id: "docsIntegrations",
|
||||
path: "integrations",
|
||||
routeBasePath: "integrations",
|
||||
sidebarPath: require.resolve("./sidebarsIntegrations.js"),
|
||||
editUrl:
|
||||
"https://github.com/goauthentik/authentik/edit/main/website/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"@docusaurus/plugin-content-docs",
|
||||
{
|
||||
id: "docsDevelopers",
|
||||
path: "developer-docs",
|
||||
routeBasePath: "developer-docs",
|
||||
sidebarPath: require.resolve("./sidebarsDev.js"),
|
||||
editUrl:
|
||||
"https://github.com/goauthentik/authentik/edit/main/website/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"@docusaurus/plugin-client-redirects",
|
||||
{
|
||||
redirects: [
|
||||
{
|
||||
to: "/docs/",
|
||||
from: ["/"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
};
|
@ -133,7 +133,7 @@ Add a new provider using the `+` button and set the following values:
|
||||
- Display name mapping: name
|
||||
- Email mapping: email
|
||||
- Quota mapping: quota (leave empty if you have skipped the [custom profile scope](#custom-profile-scope) section)
|
||||
- Groups mapping: group (leave empty if you have skipped the [custom profile scope](#custom-profile-scope) section)
|
||||
- Groups mapping: groups (leave empty if you have skipped the [custom profile scope](#custom-profile-scope) section)
|
||||
:::tip
|
||||
You need to enable the "Use group provisioning" checkmark to be able to write to this field
|
||||
:::
|
||||
|
8
website/package-lock.json
generated
8
website/package-lock.json
generated
@ -34,7 +34,7 @@
|
||||
"@docusaurus/tsconfig": "3.0.1",
|
||||
"@docusaurus/types": "3.0.1",
|
||||
"@types/react": "^18.2.48",
|
||||
"prettier": "3.2.3",
|
||||
"prettier": "3.2.4",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -13754,9 +13754,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.3.tgz",
|
||||
"integrity": "sha512-QNhUTBq+mqt1oH1dTfY3phOKNhcDdJkfttHI6u0kj7M2+c+7fmNKlgh2GhnHiqMcbxJ+a0j2igz/2jfl9QKLuw==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
|
||||
"integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
|
@ -53,7 +53,7 @@
|
||||
"@docusaurus/tsconfig": "3.0.1",
|
||||
"@docusaurus/types": "3.0.1",
|
||||
"@types/react": "^18.2.48",
|
||||
"prettier": "3.2.3",
|
||||
"prettier": "3.2.4",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
|
Reference in New Issue
Block a user