diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f78e67f49e..eeac4c9c88 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,18 +1,30 @@ [bumpversion] -current_version = 2023.10.4 +current_version = 2024.4.2 tag = True commit = True -parse = (?P\d+)\.(?P\d+)\.(?P\d+) -serialize = {major}.{minor}.{patch} +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(?:-(?P[a-zA-Z-]+)(?P[1-9]\\d*))? +serialize = + {major}.{minor}.{patch}-{rc_t}{rc_n} + {major}.{minor}.{patch} message = release: {new_version} tag_name = version/{new_version} +[bumpversion:part:rc_t] +values = + rc + final +optional_value = final + [bumpversion:file:pyproject.toml] +[bumpversion:file:package.json] + [bumpversion:file:docker-compose.yml] [bumpversion:file:schema.yml] +[bumpversion:file:blueprints/schema.json] + [bumpversion:file:authentik/__init__.py] [bumpversion:file:internal/constants/constants.go] diff --git a/.dockerignore b/.dockerignore index 352faf761e..8d20d66d65 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,3 +9,4 @@ blueprints/local .git !gen-ts-api/node_modules !gen-ts-api/dist/** +!gen-go-api/ diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 178ae15132..19c71ebaaa 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -github: [BeryJu] +custom: https://goauthentik.io/pricing/ diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index d966aee973..6c083bd864 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -9,7 +9,7 @@ assignees: "" **Describe your question/** A clear and concise description of what you're trying to do. -**Relevant infos** +**Relevant info** i.e. Version of other software you're using, specifics of your setup **Screenshots** diff --git a/.github/actions/comment-pr-instructions/action.yml b/.github/actions/comment-pr-instructions/action.yml index b8fb31eaaa..f74a99fafb 100644 --- a/.github/actions/comment-pr-instructions/action.yml +++ b/.github/actions/comment-pr-instructions/action.yml @@ -9,9 +9,6 @@ inputs: runs: using: "composite" steps: - - name: Generate config - id: ev - uses: ./.github/actions/docker-push-variables - name: Find Comment uses: peter-evans/find-comment@v2 id: fc diff --git a/.github/actions/docker-push-variables/action.yml b/.github/actions/docker-push-variables/action.yml index 7ae3d1adf9..f322c2ab66 100644 --- a/.github/actions/docker-push-variables/action.yml +++ b/.github/actions/docker-push-variables/action.yml @@ -1,64 +1,47 @@ +--- name: "Prepare docker environment variables" description: "Prepare docker environment variables" +inputs: + image-name: + required: true + description: "Docker image prefix" + image-arch: + required: false + description: "Docker image arch" + outputs: shouldBuild: description: "Whether to build image or not" value: ${{ steps.ev.outputs.shouldBuild }} - branchName: - description: "Branch name" - value: ${{ steps.ev.outputs.branchName }} - branchNameContainer: - description: "Branch name (for containers)" - value: ${{ steps.ev.outputs.branchNameContainer }} - timestamp: - description: "Timestamp" - value: ${{ steps.ev.outputs.timestamp }} + sha: description: "sha" value: ${{ steps.ev.outputs.sha }} - shortHash: - description: "shortHash" - value: ${{ steps.ev.outputs.shortHash }} + version: - description: "version" + description: "Version" value: ${{ steps.ev.outputs.version }} - versionFamily: - description: "versionFamily" - value: ${{ steps.ev.outputs.versionFamily }} + prerelease: + description: "Prerelease" + value: ${{ steps.ev.outputs.prerelease }} + + imageTags: + description: "Docker image tags" + value: ${{ steps.ev.outputs.imageTags }} + imageMainTag: + description: "Docker image main tag" + value: ${{ steps.ev.outputs.imageMainTag }} runs: using: "composite" steps: - name: Generate config id: ev - shell: python + shell: bash + env: + IMAGE_NAME: ${{ inputs.image-name }} + IMAGE_ARCH: ${{ inputs.image-arch }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | - """Helper script to get the actual branch name, docker safe""" - import configparser - import os - from time import time - - parser = configparser.ConfigParser() - parser.read(".bumpversion.cfg") - - branch_name = os.environ["GITHUB_REF"] - if os.environ.get("GITHUB_HEAD_REF", "") != "": - branch_name = os.environ["GITHUB_HEAD_REF"] - - should_build = str(os.environ.get("DOCKER_USERNAME", "") != "").lower() - version = parser.get("bumpversion", "current_version") - version_family = ".".join(version.split(".")[:-1]) - safe_branch_name = branch_name.replace("refs/heads/", "").replace("/", "-") - - sha = os.environ["GITHUB_SHA"] if not "${{ github.event.pull_request.head.sha }}" else "${{ github.event.pull_request.head.sha }}" - - with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output: - print("branchName=%s" % branch_name, file=_output) - print("branchNameContainer=%s" % safe_branch_name, file=_output) - print("timestamp=%s" % int(time()), file=_output) - print("sha=%s" % sha, file=_output) - print("shortHash=%s" % sha[:7], file=_output) - print("shouldBuild=%s" % should_build, file=_output) - print("version=%s" % version, file=_output) - print("versionFamily=%s" % version_family, file=_output) + python3 ${{ github.action_path }}/push_vars.py diff --git a/.github/actions/docker-push-variables/push_vars.py b/.github/actions/docker-push-variables/push_vars.py new file mode 100644 index 0000000000..28572b8ca4 --- /dev/null +++ b/.github/actions/docker-push-variables/push_vars.py @@ -0,0 +1,62 @@ +"""Helper script to get the actual branch name, docker safe""" + +import configparser +import os +from time import time + +parser = configparser.ConfigParser() +parser.read(".bumpversion.cfg") + +should_build = str(os.environ.get("DOCKER_USERNAME", None) is not None).lower() + +branch_name = os.environ["GITHUB_REF"] +if os.environ.get("GITHUB_HEAD_REF", "") != "": + branch_name = os.environ["GITHUB_HEAD_REF"] +safe_branch_name = branch_name.replace("refs/heads/", "").replace("/", "-").replace("'", "-") + +image_names = os.getenv("IMAGE_NAME").split(",") +image_arch = os.getenv("IMAGE_ARCH") or None + +is_pull_request = bool(os.getenv("PR_HEAD_SHA")) +is_release = "dev" not in image_names[0] + +sha = os.environ["GITHUB_SHA"] if not is_pull_request else os.getenv("PR_HEAD_SHA") + +# 2042.1.0 or 2042.1.0-rc1 +version = parser.get("bumpversion", "current_version") +# 2042.1 +version_family = ".".join(version.split("-", 1)[0].split(".")[:-1]) +prerelease = "-" in version + +image_tags = [] +if is_release: + for name in image_names: + image_tags += [ + f"{name}:{version}", + ] + if not prerelease: + image_tags += [ + f"{name}:latest", + f"{name}:{version_family}", + ] +else: + suffix = "" + if image_arch and image_arch != "amd64": + suffix = f"-{image_arch}" + for name in image_names: + image_tags += [ + f"{name}:gh-{sha}{suffix}", # Used for ArgoCD and PR comments + f"{name}:gh-{safe_branch_name}{suffix}", # For convenience + f"{name}:gh-{safe_branch_name}-{int(time())}-{sha[:7]}{suffix}", # Use by FluxCD + ] + +image_main_tag = image_tags[0] +image_tags_rendered = ",".join(image_tags) + +with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output: + print(f"shouldBuild={should_build}", file=_output) + print(f"sha={sha}", file=_output) + print(f"version={version}", file=_output) + print(f"prerelease={prerelease}", file=_output) + print(f"imageTags={image_tags_rendered}", file=_output) + print(f"imageMainTag={image_main_tag}", file=_output) diff --git a/.github/actions/docker-push-variables/test.sh b/.github/actions/docker-push-variables/test.sh new file mode 100755 index 0000000000..bfb6cca8af --- /dev/null +++ b/.github/actions/docker-push-variables/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash -x +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +GITHUB_OUTPUT=/dev/stdout \ + GITHUB_REF=ref \ + GITHUB_SHA=sha \ + IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \ + python $SCRIPT_DIR/push_vars.py diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 6e5bd99e60..20501b3f0f 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -4,7 +4,7 @@ description: "Setup authentik testing environment" inputs: postgresql_version: description: "Optional postgresql image tag" - default: "12" + default: "16" runs: using: "composite" @@ -16,25 +16,25 @@ runs: sudo apt-get update sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext - name: Setup python and restore poetry - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version-file: 'pyproject.toml' + python-version-file: "pyproject.toml" cache: "poetry" - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: web/package.json cache: "npm" cache-dependency-path: web/package-lock.json - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" - name: Setup dependencies shell: bash run: | export PSQL_TAG=${{ inputs.postgresql_version }} - docker-compose -f .github/actions/setup/docker-compose.yml up -d + docker compose -f .github/actions/setup/docker-compose.yml up -d poetry install cd web && npm ci - name: Generate config diff --git a/.github/actions/setup/docker-compose.yml b/.github/actions/setup/docker-compose.yml index 5d7a1053d3..10125a8506 100644 --- a/.github/actions/setup/docker-compose.yml +++ b/.github/actions/setup/docker-compose.yml @@ -1,8 +1,6 @@ -version: "3.7" - services: postgresql: - image: docker.io/library/postgres:${PSQL_TAG:-12} + image: docker.io/library/postgres:${PSQL_TAG:-16} volumes: - db-data:/var/lib/postgresql/data environment: diff --git a/.github/codespell-words.txt b/.github/codespell-words.txt index 71f2f1c2ca..7a7f5ef317 100644 --- a/.github/codespell-words.txt +++ b/.github/codespell-words.txt @@ -2,3 +2,6 @@ keypair keypairs hass warmup +ontext +singed +assertIn diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5aacf8ef68..0210696684 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -35,6 +35,7 @@ updates: sentry: patterns: - "@sentry/*" + - "@spotlightjs/*" babel: patterns: - "@babel/*" @@ -51,6 +52,10 @@ updates: esbuild: patterns: - "@esbuild/*" + rollup: + patterns: + - "@rollup/*" + - "rollup-*" - package-ecosystem: npm directory: "/tests/wdio" schedule: @@ -66,6 +71,7 @@ updates: sentry: patterns: - "@sentry/*" + - "@spotlightjs/*" babel: patterns: - "@babel/*" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9deccbc2ec..54e9b8b5b0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -27,7 +27,6 @@ If an API change has been made If changes to the frontend have been made - [ ] The code has been formatted (`make web`) -- [ ] The translation files have been updated (`make i18n-extract`) If applicable diff --git a/.github/workflows/api-py-publish.yml b/.github/workflows/api-py-publish.yml new file mode 100644 index 0000000000..79a1d274e0 --- /dev/null +++ b/.github/workflows/api-py-publish.yml @@ -0,0 +1,65 @@ +name: authentik-api-py-publish +on: + push: + branches: [main] + paths: + - "schema.yml" + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - id: generate_token + uses: tibdex/github-app-token@v2 + with: + app_id: ${{ secrets.GH_APP_ID }} + private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} + - uses: actions/checkout@v4 + with: + token: ${{ steps.generate_token.outputs.token }} + - name: Install poetry & deps + shell: bash + run: | + pipx install poetry || true + sudo apt-get update + sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext + - name: Setup python and restore poetry + uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + cache: "poetry" + - name: Generate API Client + run: make gen-client-py + - name: Publish package + working-directory: gen-py-api/ + run: | + poetry build + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: gen-py-api/dist/ + # We can't easily upgrade the API client being used due to poetry being poetry + # so we'll have to rely on dependabot + # - name: Upgrade / + # run: | + # export VERSION=$(cd gen-py-api && poetry version -s) + # poetry add "authentik_client=$VERSION" --allow-prereleases --lock + # - uses: peter-evans/create-pull-request@v6 + # id: cpr + # with: + # token: ${{ steps.generate_token.outputs.token }} + # branch: update-root-api-client + # commit-message: "root: bump API Client version" + # title: "root: bump API Client version" + # body: "root: bump API Client version" + # delete-branch: true + # signoff: true + # # ID from https://api.github.com/users/authentik-automation[bot] + # author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> + # - uses: peter-evans/enable-pull-request-automerge@v3 + # with: + # token: ${{ steps.generate_token.outputs.token }} + # pull-request-number: ${{ steps.cpr.outputs.pull-request-number }} + # merge-method: squash diff --git a/.github/workflows/web-api-publish.yml b/.github/workflows/api-ts-publish.yml similarity index 95% rename from .github/workflows/web-api-publish.yml rename to .github/workflows/api-ts-publish.yml index 4c617a1992..8920a0b602 100644 --- a/.github/workflows/web-api-publish.yml +++ b/.github/workflows/api-ts-publish.yml @@ -1,4 +1,4 @@ -name: authentik-web-api-publish +name: authentik-api-ts-publish on: push: branches: [main] @@ -35,7 +35,7 @@ jobs: run: | export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'` npm i @goauthentik/api@$VERSION - - uses: peter-evans/create-pull-request@v5 + - uses: peter-evans/create-pull-request@v6 id: cpr with: token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml index 4f7cf52569..9dc67ed945 100644 --- a/.github/workflows/ci-main.yml +++ b/.github/workflows/ci-main.yml @@ -1,3 +1,4 @@ +--- name: authentik-ci-main on: @@ -6,8 +7,6 @@ on: - main - next - version-* - paths-ignore: - - website pull_request: branches: - main @@ -27,10 +26,7 @@ jobs: - bandit - black - codespell - - isort - pending-migrations - - pylint - - pyright - ruff runs-on: ubuntu-latest steps: @@ -54,17 +50,12 @@ jobs: fail-fast: false matrix: psql: - - 12-alpine - 15-alpine - 16-alpine steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup authentik env - uses: ./.github/actions/setup - with: - postgresql_version: ${{ matrix.psql }} - name: checkout stable run: | # Delete all poetry envs @@ -73,10 +64,10 @@ jobs: cp authentik/lib/default.yml local.env.yml cp -R .github .. cp -R scripts .. - git checkout version/$(python -c "from authentik import __version__; print(__version__)") + git checkout $(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1) rm -rf .github/ scripts/ mv ../.github ../scripts . - - name: Setup authentik env (ensure stable deps are installed) + - name: Setup authentik env (stable) uses: ./.github/actions/setup with: postgresql_version: ${{ matrix.psql }} @@ -90,15 +81,20 @@ jobs: git clean -d -fx . git checkout $GITHUB_SHA # Delete previous poetry env - rm -rf $(poetry env info --path) + rm -rf /home/runner/.cache/pypoetry/virtualenvs/* - name: Setup authentik env (ensure latest deps are installed) uses: ./.github/actions/setup with: postgresql_version: ${{ matrix.psql }} - name: migrate to latest run: | - poetry install poetry run python -m lifecycle.migrate + - name: run tests + env: + # Test in the main database that we just migrated from the previous stable version + AUTHENTIK_POSTGRESQL__TEST__NAME: authentik + run: | + poetry run make test test-unittest: name: test-unittest - PostgreSQL ${{ matrix.psql }} runs-on: ubuntu-latest @@ -107,7 +103,6 @@ jobs: fail-fast: false matrix: psql: - - 12-alpine - 15-alpine - 16-alpine steps: @@ -121,9 +116,10 @@ jobs: poetry run make test poetry run coverage xml - if: ${{ always() }} - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: unit + token: ${{ secrets.CODECOV_TOKEN }} test-integration: runs-on: ubuntu-latest timeout-minutes: 30 @@ -132,15 +128,16 @@ jobs: - name: Setup authentik env uses: ./.github/actions/setup - name: Create k8s Kind Cluster - uses: helm/kind-action@v1.8.0 + uses: helm/kind-action@v1.10.0 - name: run integration run: | poetry run coverage run manage.py test tests/integration poetry run coverage xml - if: ${{ always() }} - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: integration + token: ${{ secrets.CODECOV_TOKEN }} test-e2e: name: test-e2e (${{ matrix.job.name }}) runs-on: ubuntu-latest @@ -161,6 +158,8 @@ jobs: glob: tests/e2e/test_provider_ldap* tests/e2e/test_source_ldap* - name: radius glob: tests/e2e/test_provider_radius* + - name: scim + glob: tests/e2e/test_source_scim* - name: flows glob: tests/e2e/test_flows* steps: @@ -169,9 +168,9 @@ jobs: uses: ./.github/actions/setup - name: Setup e2e env (chrome, etc) run: | - docker-compose -f tests/e2e/docker-compose.yml up -d + 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/**') }} @@ -187,9 +186,10 @@ jobs: poetry run coverage run manage.py test ${{ matrix.job.glob }} poetry run coverage xml - if: ${{ always() }} - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: e2e + token: ${{ secrets.CODECOV_TOKEN }} ci-core-mark: needs: - lint @@ -202,6 +202,12 @@ jobs: steps: - run: echo mark build: + strategy: + fail-fast: false + matrix: + arch: + - amd64 + - arm64 needs: ci-core-mark runs-on: ubuntu-latest permissions: @@ -221,9 +227,12 @@ jobs: id: ev env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + with: + image-name: ghcr.io/goauthentik/dev-server + image-arch: ${{ matrix.arch }} - name: Login to Container Registry - uses: docker/login-action@v3 if: ${{ steps.ev.outputs.shouldBuild == 'true' }} + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -231,74 +240,40 @@ jobs: - name: generate ts client run: make gen-client-ts - name: Build Docker Image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . secrets: | GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }} GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }} + tags: ${{ steps.ev.outputs.imageTags }} push: ${{ steps.ev.outputs.shouldBuild == 'true' }} - tags: | - ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }} - ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.sha }} - ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.shortHash }} build-args: | GIT_BUILD_HASH=${{ steps.ev.outputs.sha }} - VERSION=${{ steps.ev.outputs.version }} - VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache + cache-to: type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache,mode=max + platforms: linux/${{ matrix.arch }} + pr-comment: + needs: + - build + runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' }} + permissions: + # Needed to write comments on PRs + pull-requests: write + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: prepare variables + uses: ./.github/actions/docker-push-variables + id: ev + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + with: + image-name: ghcr.io/goauthentik/dev-server - name: Comment on PR - if: github.event_name == 'pull_request' - continue-on-error: true uses: ./.github/actions/comment-pr-instructions with: - tag: gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.shortHash }} - build-arm64: - needs: ci-core-mark - runs-on: ubuntu-latest - permissions: - # Needed to upload contianer images to ghcr.io - packages: write - timeout-minutes: 120 - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v3.0.0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: prepare variables - uses: ./.github/actions/docker-push-variables - id: ev - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - - name: Login to Container Registry - uses: docker/login-action@v3 - if: ${{ steps.ev.outputs.shouldBuild == 'true' }} - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: generate ts client - run: make gen-client-ts - - name: Build Docker Image - uses: docker/build-push-action@v5 - with: - context: . - secrets: | - GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }} - GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }} - push: ${{ steps.ev.outputs.shouldBuild == 'true' }} - tags: | - ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-arm64 - ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.sha }}-arm64 - ghcr.io/goauthentik/dev-server:gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.shortHash }}-arm64 - build-args: | - GIT_BUILD_HASH=${{ steps.ev.outputs.sha }} - VERSION=${{ steps.ev.outputs.version }} - VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }} - platforms: linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max + tag: gh-${{ steps.ev.outputs.imageMainTag }} diff --git a/.github/workflows/ci-outpost.yml b/.github/workflows/ci-outpost.yml index 196fa0b3b1..23b7283b57 100644 --- a/.github/workflows/ci-outpost.yml +++ b/.github/workflows/ci-outpost.yml @@ -1,3 +1,4 @@ +--- name: authentik-ci-outpost on: @@ -28,7 +29,7 @@ jobs: - name: Generate API run: make gen-client-go - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: version: v1.54.2 args: --timeout 5000s --verbose @@ -65,6 +66,7 @@ jobs: - proxy - ldap - radius + - rac runs-on: ubuntu-latest permissions: # Needed to upload contianer images to ghcr.io @@ -82,9 +84,11 @@ jobs: id: ev env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + with: + image-name: ghcr.io/goauthentik/dev-${{ matrix.type }} - name: Login to Container Registry - uses: docker/login-action@v3 if: ${{ steps.ev.outputs.shouldBuild == 'true' }} + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -92,21 +96,17 @@ jobs: - name: Generate API run: make gen-client-go - name: Build Docker Image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: - push: ${{ steps.ev.outputs.shouldBuild == 'true' }} - tags: | - ghcr.io/goauthentik/dev-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchNameContainer }} - ghcr.io/goauthentik/dev-${{ matrix.type }}:gh-${{ steps.ev.outputs.sha }} + tags: ${{ steps.ev.outputs.imageTags }} file: ${{ matrix.type }}.Dockerfile + push: ${{ steps.ev.outputs.shouldBuild == 'true' }} build-args: | GIT_BUILD_HASH=${{ steps.ev.outputs.sha }} - VERSION=${{ steps.ev.outputs.version }} - VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }} platforms: linux/amd64,linux/arm64 context: . - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache + cache-to: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache,mode=max build-binary: timeout-minutes: 120 needs: @@ -119,6 +119,7 @@ jobs: - proxy - ldap - radius + - rac goos: [linux] goarch: [amd64, arm64] steps: diff --git a/.github/workflows/ci-web.yml b/.github/workflows/ci-web.yml index 43ca0a168f..897104bb14 100644 --- a/.github/workflows/ci-web.yml +++ b/.github/workflows/ci-web.yml @@ -34,6 +34,13 @@ jobs: - name: Eslint working-directory: ${{ matrix.project }}/ run: npm run lint + lint-lockfile: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - working-directory: web/ + run: | + [ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ] lint-build: runs-on: ubuntu-latest steps: @@ -95,6 +102,7 @@ jobs: run: npm run lit-analyse ci-web-mark: needs: + - lint-lockfile - lint-eslint - lint-prettier - lint-lit-analyse diff --git a/.github/workflows/ci-website.yml b/.github/workflows/ci-website.yml index 78a3a8f8a1..001b0d8041 100644 --- a/.github/workflows/ci-website.yml +++ b/.github/workflows/ci-website.yml @@ -12,6 +12,13 @@ on: - version-* jobs: + lint-lockfile: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - working-directory: website/ + run: | + [ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ] lint-prettier: runs-on: ubuntu-latest steps: @@ -48,7 +55,6 @@ jobs: matrix: job: - build - - build-docs-only steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -63,6 +69,7 @@ jobs: run: npm run ${{ matrix.job }} ci-website-mark: needs: + - lint-lockfile - lint-prettier - test - build diff --git a/.github/workflows/gen-update-webauthn-mds.yml b/.github/workflows/gen-update-webauthn-mds.yml new file mode 100644 index 0000000000..c89aa4c2f6 --- /dev/null +++ b/.github/workflows/gen-update-webauthn-mds.yml @@ -0,0 +1,43 @@ +name: authentik-gen-update-webauthn-mds +on: + workflow_dispatch: + schedule: + - cron: '30 1 1,15 * *' + +env: + POSTGRES_DB: authentik + POSTGRES_USER: authentik + POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - id: generate_token + uses: tibdex/github-app-token@v2 + with: + app_id: ${{ secrets.GH_APP_ID }} + private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} + - uses: actions/checkout@v4 + with: + token: ${{ steps.generate_token.outputs.token }} + - name: Setup authentik env + uses: ./.github/actions/setup + - run: poetry run ak update_webauthn_mds + - uses: peter-evans/create-pull-request@v6 + id: cpr + with: + token: ${{ steps.generate_token.outputs.token }} + branch: update-fido-mds-client + commit-message: "stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs" + title: "stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs" + body: "stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs" + delete-branch: true + signoff: true + # ID from https://api.github.com/users/authentik-automation[bot] + author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> + - uses: peter-evans/enable-pull-request-automerge@v3 + with: + token: ${{ steps.generate_token.outputs.token }} + pull-request-number: ${{ steps.cpr.outputs.pull-request-number }} + merge-method: squash diff --git a/.github/workflows/image-compress.yml b/.github/workflows/image-compress.yml index 02a18553bc..4c186d220f 100644 --- a/.github/workflows/image-compress.yml +++ b/.github/workflows/image-compress.yml @@ -42,7 +42,7 @@ jobs: with: githubToken: ${{ steps.generate_token.outputs.token }} compressOnly: ${{ github.event_name != 'pull_request' }} - - uses: peter-evans/create-pull-request@v5 + - uses: peter-evans/create-pull-request@v6 if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}" id: cpr with: diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index c3c6a0d48e..fefd0b694d 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -1,3 +1,4 @@ +--- name: authentik-on-release on: @@ -19,6 +20,10 @@ jobs: - name: prepare variables uses: ./.github/actions/docker-push-variables id: ev + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + with: + image-name: ghcr.io/goauthentik/server,beryju/authentik - name: Docker Login Registry uses: docker/login-action@v3 with: @@ -35,24 +40,15 @@ jobs: mkdir -p ./gen-ts-api mkdir -p ./gen-go-api - name: Build Docker Image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . - push: ${{ github.event_name == 'release' }} + push: true secrets: | GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }} GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }} - tags: | - beryju/authentik:${{ steps.ev.outputs.version }}, - beryju/authentik:${{ steps.ev.outputs.versionFamily }}, - beryju/authentik:latest, - ghcr.io/goauthentik/server:${{ steps.ev.outputs.version }}, - ghcr.io/goauthentik/server:${{ steps.ev.outputs.versionFamily }}, - ghcr.io/goauthentik/server:latest + tags: ${{ steps.ev.outputs.imageTags }} platforms: linux/amd64,linux/arm64 - build-args: | - VERSION=${{ steps.ev.outputs.version }} - VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }} build-outpost: runs-on: ubuntu-latest permissions: @@ -65,6 +61,7 @@ jobs: - proxy - ldap - radius + - rac steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 @@ -77,6 +74,10 @@ jobs: - name: prepare variables uses: ./.github/actions/docker-push-variables id: ev + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + with: + image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }} - name: make empty clients run: | mkdir -p ./gen-ts-api @@ -93,22 +94,13 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build Docker Image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: - push: ${{ github.event_name == 'release' }} - tags: | - beryju/authentik-${{ matrix.type }}:${{ steps.ev.outputs.version }}, - beryju/authentik-${{ matrix.type }}:${{ steps.ev.outputs.versionFamily }}, - beryju/authentik-${{ matrix.type }}:latest, - ghcr.io/goauthentik/${{ matrix.type }}:${{ steps.ev.outputs.version }}, - ghcr.io/goauthentik/${{ matrix.type }}:${{ steps.ev.outputs.versionFamily }}, - ghcr.io/goauthentik/${{ matrix.type }}:latest + push: true + tags: ${{ steps.ev.outputs.imageTags }} file: ${{ matrix.type }}.Dockerfile platforms: linux/amd64,linux/arm64 context: . - build-args: | - VERSION=${{ steps.ev.outputs.version }} - VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }} build-outpost-binary: timeout-minutes: 120 runs-on: ubuntu-latest @@ -163,12 +155,12 @@ jobs: - uses: actions/checkout@v4 - name: Run test suite in final docker images run: | - echo "PG_PASS=$(openssl rand -base64 32)" >> .env - echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env - docker-compose pull -q - docker-compose up --no-start - docker-compose start postgresql redis - docker-compose run -u root server test-all + echo "PG_PASS=$(openssl rand 32 | base64)" >> .env + echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64)" >> .env + docker compose pull -q + docker compose up --no-start + docker compose start postgresql redis + docker compose run -u root server test-all sentry-release: needs: - build-server @@ -180,15 +172,18 @@ jobs: - name: prepare variables uses: ./.github/actions/docker-push-variables id: ev + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + with: + image-name: ghcr.io/goauthentik/server - name: Get static files from docker image run: | - docker pull ghcr.io/goauthentik/server:latest - container=$(docker container create ghcr.io/goauthentik/server:latest) + docker pull ${{ steps.ev.outputs.imageMainTag }} + container=$(docker container create ${{ steps.ev.outputs.imageMainTag }}) docker cp ${container}:web/ . - name: Create a Sentry.io release uses: getsentry/action-release@v1 continue-on-error: true - if: ${{ github.event_name == 'release' }} env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: authentik-security-inc diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index a0b896a92b..0da13b3c33 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -1,3 +1,4 @@ +--- name: authentik-on-tag on: @@ -13,28 +14,28 @@ jobs: - uses: actions/checkout@v4 - name: Pre-release test run: | - echo "PG_PASS=$(openssl rand -base64 32)" >> .env - echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env + echo "PG_PASS=$(openssl rand 32 | base64)" >> .env + echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64)" >> .env docker buildx install mkdir -p ./gen-ts-api docker build -t testing:latest . echo "AUTHENTIK_IMAGE=testing" >> .env echo "AUTHENTIK_TAG=latest" >> .env - docker-compose up --no-start - docker-compose start postgresql redis - docker-compose run -u root server test-all + docker compose up --no-start + docker compose start postgresql redis + docker compose run -u root server test-all - id: generate_token uses: tibdex/github-app-token@v2 with: app_id: ${{ secrets.GH_APP_ID }} private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - name: Extract version number - id: get_version - uses: actions/github-script@v7 + - name: prepare variables + uses: ./.github/actions/docker-push-variables + id: ev + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} with: - github-token: ${{ steps.generate_token.outputs.token }} - script: | - return context.payload.ref.replace(/\/refs\/tags\/version\//, ''); + image-name: ghcr.io/goauthentik/server - name: Create Release id: create_release uses: actions/create-release@v1.1.4 @@ -42,6 +43,6 @@ jobs: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} with: tag_name: ${{ github.ref }} - release_name: Release ${{ steps.get_version.outputs.result }} + release_name: Release ${{ steps.ev.outputs.version }} draft: true - prerelease: false + prerelease: ${{ steps.ev.outputs.prerelease == 'true' }} diff --git a/.github/workflows/repo-stale.yml b/.github/workflows/repo-stale.yml index 24f0ac0fb0..4918171953 100644 --- a/.github/workflows/repo-stale.yml +++ b/.github/workflows/repo-stale.yml @@ -23,7 +23,7 @@ jobs: repo-token: ${{ steps.generate_token.outputs.token }} days-before-stale: 60 days-before-close: 7 - exempt-issue-labels: pinned,security,pr_wanted,enhancement,bug/confirmed,enhancement/confirmed,question + exempt-issue-labels: pinned,security,pr_wanted,enhancement,bug/confirmed,enhancement/confirmed,question,status/reviewing stale-issue-label: wontfix stale-issue-message: > This issue has been automatically marked as stale because it has not had diff --git a/.github/workflows/translation-advice.yml b/.github/workflows/translation-advice.yml index ad76424fa4..c79c450474 100644 --- a/.github/workflows/translation-advice.yml +++ b/.github/workflows/translation-advice.yml @@ -19,14 +19,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Find Comment - uses: peter-evans/find-comment@v2 + uses: peter-evans/find-comment@v3 id: fc with: issue-number: ${{ github.event.pull_request.number }} comment-author: "github-actions[bot]" body-includes: authentik translations instructions - name: Create or update comment - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/translation-compile.yml b/.github/workflows/translation-extract-compile.yml similarity index 57% rename from .github/workflows/translation-compile.yml rename to .github/workflows/translation-extract-compile.yml index e6d075af7b..eee769ff2e 100644 --- a/.github/workflows/translation-compile.yml +++ b/.github/workflows/translation-extract-compile.yml @@ -1,9 +1,8 @@ -name: authentik-backend-translate-compile +--- +name: authentik-backend-translate-extract-compile on: - push: - branches: [main] - paths: - - "locale/**" + schedule: + - cron: "0 0 * * *" # every day at midnight workflow_dispatch: env: @@ -25,16 +24,20 @@ jobs: token: ${{ steps.generate_token.outputs.token }} - name: Setup authentik env uses: ./.github/actions/setup + - name: run extract + run: | + poetry run make i18n-extract - name: run compile - run: poetry run ak compilemessages + run: | + poetry run ak compilemessages + make web-check-compile - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 - id: cpr + uses: peter-evans/create-pull-request@v6 with: token: ${{ steps.generate_token.outputs.token }} - branch: compile-backend-translation - commit-message: "core: compile backend translations" - title: "core: compile backend translations" - body: "core: compile backend translations" + branch: extract-compile-backend-translation + commit-message: "core, web: update translations" + title: "core, web: update translations" + body: "core, web: update translations" delete-branch: true signoff: true diff --git a/.vscode/extensions.json b/.vscode/extensions.json index dd280d682e..53a07a56d2 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -10,8 +10,7 @@ "Gruntfuggly.todo-tree", "mechatroner.rainbow-csv", "ms-python.black-formatter", - "ms-python.isort", - "ms-python.pylint", + "charliermarsh.ruff", "ms-python.python", "ms-python.vscode-pylance", "ms-python.black-formatter", diff --git a/.vscode/settings.json b/.vscode/settings.json index 218800d1ed..ed87f96869 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,20 +4,21 @@ "asgi", "authentik", "authn", + "entra", "goauthentik", "jwks", + "kubernetes", "oidc", "openid", + "passwordless", "plex", "saml", - "totp", - "webauthn", - "traefik", - "passwordless", - "kubernetes", - "sso", - "slo", "scim", + "slo", + "sso", + "totp", + "traefik", + "webauthn", ], "todo-tree.tree.showCountsInTree": true, "todo-tree.tree.showBadges": true, diff --git a/CODEOWNERS b/CODEOWNERS index 5cdb09c14b..776bbae630 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -11,6 +11,8 @@ scripts/ @goauthentik/backend tests/ @goauthentik/backend pyproject.toml @goauthentik/backend poetry.lock @goauthentik/backend +go.mod @goauthentik/backend +go.sum @goauthentik/backend # Infrastructure .github/ @goauthentik/infrastructure Dockerfile @goauthentik/infrastructure diff --git a/Dockerfile b/Dockerfile index 629d3258b1..745078bf2a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # Stage 1: Build website -FROM --platform=${BUILDPLATFORM} docker.io/node:21 as website-builder +FROM --platform=${BUILDPLATFORM} docker.io/node:22 as website-builder ENV NODE_ENV=production @@ -14,12 +14,13 @@ RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.js COPY ./website /work/website/ COPY ./blueprints /work/blueprints/ +COPY ./schema.yml /work/ COPY ./SECURITY.md /work/ -RUN npm run build-docs-only +RUN npm run build-bundled # Stage 2: Build webui -FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder +FROM --platform=${BUILDPLATFORM} docker.io/node:22 as web-builder ENV NODE_ENV=production @@ -30,6 +31,7 @@ RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \ --mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \ npm ci --include=dev +COPY ./package.json /work COPY ./web /work/web/ COPY ./website /work/website/ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api @@ -37,7 +39,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api RUN npm run build # Stage 3: Build go proxy -FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.5-bookworm AS go-builder +FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS go-builder ARG TARGETOS ARG TARGETARCH @@ -48,6 +50,11 @@ ARG GOARCH=$TARGETARCH WORKDIR /go/src/goauthentik.io +RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ + dpkg --add-architecture arm64 && \ + apt-get update && \ + apt-get install -y --no-install-recommends crossbuild-essential-arm64 gcc-aarch64-linux-gnu + RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ --mount=type=bind,target=/go/src/goauthentik.io/go.sum,src=./go.sum \ --mount=type=cache,target=/go/pkg/mod \ @@ -62,17 +69,17 @@ COPY ./internal /go/src/goauthentik.io/internal COPY ./go.mod /go/src/goauthentik.io/go.mod COPY ./go.sum /go/src/goauthentik.io/go.sum -ENV CGO_ENABLED=0 - 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 + if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \ + CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \ + go build -o /go/authentik ./cmd/server # Stage 4: MaxMind GeoIP -FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.0 as geoip +FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.0.1 as geoip -ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City" -ENV GEOIPUPDATE_VERBOSE="true" +ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN" +ENV GEOIPUPDATE_VERBOSE="1" ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID" ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY" @@ -83,7 +90,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ /bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" # Stage 5: Python dependencies -FROM docker.io/python:3.12.1-slim-bookworm AS python-deps +FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS python-deps WORKDIR /ak-root/poetry @@ -96,19 +103,21 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloa RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ apt-get update && \ # Required for installing pip packages - apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev zlib1g-dev libpq-dev + apt-get install -y --no-install-recommends build-essential pkg-config libpq-dev RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \ --mount=type=bind,target=./poetry.lock,src=./poetry.lock \ --mount=type=cache,target=/root/.cache/pip \ --mount=type=cache,target=/root/.cache/pypoetry \ python -m venv /ak-root/venv/ && \ + bash -c "source ${VENV_PATH}/bin/activate && \ pip3 install --upgrade pip && \ pip3 install poetry && \ - poetry install --only=main --no-ansi --no-interaction + poetry install --only=main --no-ansi --no-interaction --no-root && \ + pip install --force-reinstall /wheels/*" # Stage 6: Run -FROM docker.io/python:3.12.1-slim-bookworm AS final-image +FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS final-image ARG GIT_BUILD_HASH ARG VERSION @@ -125,7 +134,7 @@ WORKDIR / # We cannot cache this layer otherwise we'll end up with a bigger image RUN apt-get update && \ # Required for runtime - apt-get install -y --no-install-recommends libpq5 openssl libxmlsec1-openssl libmaxminddb0 ca-certificates && \ + apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates && \ # Required for bootstrap & healtcheck apt-get install -y --no-install-recommends runit && \ apt-get clean && \ @@ -149,7 +158,7 @@ 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=website-builder /work/website/build/ /website/help/ COPY --from=geoip /usr/share/GeoIP /geoip USER 1000 @@ -161,6 +170,8 @@ ENV TMPDIR=/dev/shm/ \ VENV_PATH="/ak-root/venv" \ POETRY_VIRTUALENVS_CREATE=false +ENV GOFIPS=1 + HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ] ENTRYPOINT [ "dumb-init", "--", "ak" ] diff --git a/Makefile b/Makefile index c649ff2307..cfba239fcc 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,13 @@ PWD = $(shell pwd) UID = $(shell id -u) GID = $(shell id -g) NPM_VERSION = $(shell python -m scripts.npm_version) -PY_SOURCES = authentik tests scripts lifecycle +PY_SOURCES = authentik tests scripts lifecycle .github DOCKER_IMAGE ?= "authentik:test" +GEN_API_TS = "gen-ts-api" +GEN_API_PY = "gen-py-api" +GEN_API_GO = "gen-go-api" + pg_user := $(shell python -m authentik.lib.config postgresql.user 2>/dev/null) pg_host := $(shell python -m authentik.lib.config postgresql.host 2>/dev/null) pg_name := $(shell python -m authentik.lib.config postgresql.name 2>/dev/null) @@ -15,6 +19,7 @@ pg_name := $(shell python -m authentik.lib.config postgresql.name 2>/dev/null) CODESPELL_ARGS = -D - -D .github/codespell-dictionary.txt \ -I .github/codespell-words.txt \ -S 'web/src/locales/**' \ + -S 'website/developer-docs/api/reference/**' \ authentik \ internal \ cmd \ @@ -42,12 +47,12 @@ test-go: go test -timeout 0 -v -race -cover ./... test-docker: ## Run all tests in a docker-compose - echo "PG_PASS=$(openssl rand -base64 32)" >> .env - echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env - docker-compose pull -q - docker-compose up --no-start - docker-compose start postgresql redis - docker-compose run -u root server test-all + echo "PG_PASS=$(shell openssl rand 32 | base64)" >> .env + echo "AUTHENTIK_SECRET_KEY=$(shell openssl rand 32 | base64)" >> .env + docker compose pull -q + docker compose up --no-start + docker compose start postgresql redis + docker compose run -u root server test-all rm -f .env test: ## Run the server tests and produce a coverage report (locally) @@ -56,27 +61,34 @@ test: ## Run the server tests and produce a coverage report (locally) coverage report lint-fix: ## Lint and automatically fix errors in the python source code. Reports spelling errors. - isort $(PY_SOURCES) black $(PY_SOURCES) - ruff $(PY_SOURCES) + ruff check --fix $(PY_SOURCES) codespell -w $(CODESPELL_ARGS) lint: ## Lint the python and golang sources - bandit -r $(PY_SOURCES) -x node_modules - ./web/node_modules/.bin/pyright $(PY_SOURCES) - pylint $(PY_SOURCES) + bandit -r $(PY_SOURCES) -x web/node_modules -x tests/wdio/node_modules -x website/node_modules golangci-lint run -v +core-install: + poetry install + migrate: ## Run the Authentik Django server's migrations python -m lifecycle.migrate -i18n-extract: i18n-extract-core web-i18n-extract ## Extract strings that require translation into files to send to a translation service +i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service -i18n-extract-core: - ak makemessages --ignore web --ignore internal --ignore web --ignore web-api --ignore website -l en +core-i18n-extract: + ak makemessages \ + --add-location file \ + --no-obsolete \ + --ignore web \ + --ignore internal \ + --ignore ${GEN_API_TS} \ + --ignore ${GEN_API_GO} \ + --ignore website \ + -l en -install: web-install website-install ## Install all requires dependencies for `web`, `website` and `core` - poetry install +install: web-install website-install core-install ## Install all requires dependencies for `web`, `website` and `core` dev-drop-db: dropdb -U ${pg_user} -h ${pg_host} ${pg_name} @@ -94,8 +106,14 @@ dev-reset: dev-drop-db dev-create-db migrate ## Drop and restore the Authentik ######################### gen-build: ## Extract the schema from the database - AUTHENTIK_DEBUG=true ak make_blueprint_schema > blueprints/schema.json - AUTHENTIK_DEBUG=true ak spectacular --file schema.yml + AUTHENTIK_DEBUG=true \ + AUTHENTIK_TENANTS__ENABLED=true \ + AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ + ak make_blueprint_schema > blueprints/schema.json + AUTHENTIK_DEBUG=true \ + AUTHENTIK_TENANTS__ENABLED=true \ + AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \ + ak spectacular --file schema.yml gen-changelog: ## (Release) generate the changelog based from the commits since the last tag git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md @@ -106,7 +124,7 @@ gen-diff: ## (Release) generate the changelog diff between the current schema a docker run \ --rm -v ${PWD}:/local \ --user ${UID}:${GID} \ - docker.io/openapitools/openapi-diff:2.1.0-beta.6 \ + docker.io/openapitools/openapi-diff:2.1.0-beta.8 \ --markdown /local/diff.md \ /local/old_schema.yml /local/schema.yml rm old_schema.yml @@ -114,47 +132,69 @@ gen-diff: ## (Release) generate the changelog diff between the current schema a sed -i 's/}/}/g' diff.md npx prettier --write diff.md -gen-clean: - rm -rf web/api/src/ - rm -rf api/ +gen-clean-ts: ## Remove generated API client for Typescript + rm -rf ./${GEN_API_TS}/ + rm -rf ./web/node_modules/@goauthentik/api/ -gen-client-ts: ## Build and install the authentik API for Typescript into the authentik UI Application +gen-clean-go: ## Remove generated API client for Go + rm -rf ./${GEN_API_GO}/ + +gen-clean-py: ## Remove generated API client for Python + rm -rf ./${GEN_API_PY}/ + +gen-clean: gen-clean-ts gen-clean-go gen-clean-py ## Remove generated API clients + +gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescript into the authentik UI Application docker run \ --rm -v ${PWD}:/local \ --user ${UID}:${GID} \ docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \ -i /local/schema.yml \ -g typescript-fetch \ - -o /local/gen-ts-api \ + -o /local/${GEN_API_TS} \ -c /local/scripts/api-ts-config.yaml \ --additional-properties=npmVersion=${NPM_VERSION} \ --git-repo-id authentik \ --git-user-id goauthentik mkdir -p web/node_modules/@goauthentik/api - cd gen-ts-api && npm i - \cp -rfv gen-ts-api/* web/node_modules/@goauthentik/api + cd ./${GEN_API_TS} && npm i + \cp -rf ./${GEN_API_TS}/* web/node_modules/@goauthentik/api -gen-client-go: ## Build and install the authentik API for Golang - mkdir -p ./gen-go-api ./gen-go-api/templates - wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml -O ./gen-go-api/config.yaml - wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache -O ./gen-go-api/templates/README.mustache - wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache -O ./gen-go-api/templates/go.mod.mustache - cp schema.yml ./gen-go-api/ +gen-client-py: gen-clean-py ## Build and install the authentik API for Python docker run \ - --rm -v ${PWD}/gen-go-api:/local \ + --rm -v ${PWD}:/local \ + --user ${UID}:${GID} \ + docker.io/openapitools/openapi-generator-cli:v7.4.0 generate \ + -i /local/schema.yml \ + -g python \ + -o /local/${GEN_API_PY} \ + -c /local/scripts/api-py-config.yaml \ + --additional-properties=packageVersion=${NPM_VERSION} \ + --git-repo-id authentik \ + --git-user-id goauthentik + pip install ./${GEN_API_PY} + +gen-client-go: gen-clean-go ## Build and install the authentik API for Golang + mkdir -p ./${GEN_API_GO} ./${GEN_API_GO}/templates + wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml -O ./${GEN_API_GO}/config.yaml + wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache -O ./${GEN_API_GO}/templates/README.mustache + wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache -O ./${GEN_API_GO}/templates/go.mod.mustache + cp schema.yml ./${GEN_API_GO}/ + docker run \ + --rm -v ${PWD}/${GEN_API_GO}:/local \ --user ${UID}:${GID} \ docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \ -i /local/schema.yml \ -g go \ -o /local/ \ -c /local/config.yaml - go mod edit -replace goauthentik.io/api/v3=./gen-go-api - rm -rf ./gen-go-api/config.yaml ./gen-go-api/templates/ + go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO} + rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/ gen-dev-config: ## Generate a local development config file python -m scripts.generate_config -gen: gen-build gen-clean gen-client-ts +gen: gen-build gen-client-ts ######################### ## Web @@ -163,7 +203,7 @@ gen: gen-build gen-clean gen-client-ts web-build: web-install ## Build the Authentik UI cd web && npm run build -web: web-lint-fix web-lint web-check-compile web-i18n-extract ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it +web: web-lint-fix web-lint web-check-compile ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it web-install: ## Install the necessary libraries to build the Authentik UI cd web && npm ci @@ -213,6 +253,7 @@ website-watch: ## Build and watch the documentation website, updating automatic ######################### docker: ## Build a docker image of the current source tree + mkdir -p ${GEN_API_TS} DOCKER_BUILDKIT=1 docker build . --progress plain --tag ${DOCKER_IMAGE} ######################### @@ -225,9 +266,6 @@ ci--meta-debug: python -V node --version -ci-pylint: ci--meta-debug - pylint $(PY_SOURCES) - ci-black: ci--meta-debug black --check $(PY_SOURCES) @@ -237,14 +275,8 @@ ci-ruff: ci--meta-debug ci-codespell: ci--meta-debug codespell $(CODESPELL_ARGS) -s -ci-isort: ci--meta-debug - isort --check $(PY_SOURCES) - ci-bandit: ci--meta-debug bandit -r $(PY_SOURCES) -ci-pyright: ci--meta-debug - ./web/node_modules/.bin/pyright $(PY_SOURCES) - ci-pending-migrations: ci--meta-debug ak makemigrations --check diff --git a/README.md b/README.md index 8f5f9c2a95..e87db3f214 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ For bigger setups, there is a Helm Chart [here](https://github.com/goauthentik/h ## Screenshots -| Light | Dark | -| ------------------------------------------------------ | ----------------------------------------------------- | -| ![](https://goauthentik.io/img/screen_apps_light.jpg) | ![](https://goauthentik.io/img/screen_apps_dark.jpg) | -| ![](https://goauthentik.io/img/screen_admin_light.jpg) | ![](https://goauthentik.io/img/screen_admin_dark.jpg) | +| Light | Dark | +| ----------------------------------------------------------- | ---------------------------------------------------------- | +| ![](https://docs.goauthentik.io/img/screen_apps_light.jpg) | ![](https://docs.goauthentik.io/img/screen_apps_dark.jpg) | +| ![](https://docs.goauthentik.io/img/screen_admin_light.jpg) | ![](https://docs.goauthentik.io/img/screen_admin_dark.jpg) | ## Development diff --git a/SECURITY.md b/SECURITY.md index 9bb674f232..4fefed8ce3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -18,10 +18,10 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni (.x being the latest patch release for each version) -| Version | Supported | -| --- | --- | -| 2023.6.x | ✅ | -| 2023.8.x | ✅ | +| Version | Supported | +| --------- | --------- | +| 2023.10.x | ✅ | +| 2024.2.x | ✅ | ## Reporting a Vulnerability @@ -31,12 +31,12 @@ To report a vulnerability, send an email to [security@goauthentik.io](mailto:se authentik reserves the right to reclassify CVSS as necessary. To determine severity, we will use the CVSS calculator from NVD (https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator). The calculated CVSS score will then be translated into one of the following categories: -| Score | Severity | -| --- | --- | -| 0.0 | None | -| 0.1 – 3.9 | Low | -| 4.0 – 6.9 | Medium | -| 7.0 – 8.9 | High | +| Score | Severity | +| ---------- | -------- | +| 0.0 | None | +| 0.1 – 3.9 | Low | +| 4.0 – 6.9 | Medium | +| 7.0 – 8.9 | High | | 9.0 – 10.0 | Critical | ## Disclosure process diff --git a/authentik/__init__.py b/authentik/__init__.py index fc368ceeb6..468069e6e1 100644 --- a/authentik/__init__.py +++ b/authentik/__init__.py @@ -1,12 +1,12 @@ """authentik root module""" -from os import environ -from typing import Optional -__version__ = "2023.10.4" +from os import environ + +__version__ = "2024.4.2" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" -def get_build_hash(fallback: Optional[str] = None) -> str: +def get_build_hash(fallback: str | None = None) -> str: """Get build hash""" build_hash = environ.get(ENV_GIT_HASH_KEY, fallback if fallback else "") return fallback if build_hash == "" and fallback else build_hash diff --git a/authentik/admin/api/meta.py b/authentik/admin/api/meta.py index 52640b8c50..bdece26f5c 100644 --- a/authentik/admin/api/meta.py +++ b/authentik/admin/api/meta.py @@ -1,4 +1,5 @@ """Meta API""" + from drf_spectacular.utils import extend_schema from rest_framework.fields import CharField from rest_framework.permissions import IsAuthenticated diff --git a/authentik/admin/api/metrics.py b/authentik/admin/api/metrics.py index af32662b17..6929767ecf 100644 --- a/authentik/admin/api/metrics.py +++ b/authentik/admin/api/metrics.py @@ -1,4 +1,5 @@ """authentik administration metrics""" + from datetime import timedelta from django.db.models.functions import ExtractHour diff --git a/authentik/admin/api/system.py b/authentik/admin/api/system.py index 5a70071539..ac9df17e71 100644 --- a/authentik/admin/api/system.py +++ b/authentik/admin/api/system.py @@ -1,18 +1,23 @@ """authentik administration overview""" + import platform from datetime import datetime +from ssl import OPENSSL_VERSION from sys import version as python_version from typing import TypedDict +from cryptography.hazmat.backends.openssl.backend import backend from django.utils.timezone import now from drf_spectacular.utils import extend_schema -from gunicorn import version_info as gunicorn_version from rest_framework.fields import SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView +from authentik import get_full_version from authentik.core.api.utils import PassiveSerializer +from authentik.enterprise.license import LicenseKey +from authentik.lib.config import CONFIG from authentik.lib.utils.reflection import get_env from authentik.outposts.apps import MANAGED_OUTPOST from authentik.outposts.models import Outpost @@ -23,11 +28,13 @@ class RuntimeDict(TypedDict): """Runtime information""" python_version: str - gunicorn_version: str environment: str architecture: str platform: str uname: str + openssl_version: str + openssl_fips_enabled: bool | None + authentik_version: str class SystemInfoSerializer(PassiveSerializer): @@ -37,8 +44,9 @@ class SystemInfoSerializer(PassiveSerializer): http_host = SerializerMethodField() http_is_secure = SerializerMethodField() runtime = SerializerMethodField() - tenant = SerializerMethodField() + brand = SerializerMethodField() server_time = SerializerMethodField() + embedded_outpost_disabled = SerializerMethodField() embedded_outpost_host = SerializerMethodField() def get_http_headers(self, request: Request) -> dict[str, str]: @@ -61,22 +69,30 @@ class SystemInfoSerializer(PassiveSerializer): def get_runtime(self, request: Request) -> RuntimeDict: """Get versions""" return { - "python_version": python_version, - "gunicorn_version": ".".join(str(x) for x in gunicorn_version), - "environment": get_env(), "architecture": platform.machine(), + "authentik_version": get_full_version(), + "environment": get_env(), + "openssl_fips_enabled": ( + backend._fips_enabled if LicenseKey.get_total().is_valid() else None + ), + "openssl_version": OPENSSL_VERSION, "platform": platform.platform(), + "python_version": python_version, "uname": " ".join(platform.uname()), } - def get_tenant(self, request: Request) -> str: - """Currently active tenant""" - return str(request._request.tenant) + def get_brand(self, request: Request) -> str: + """Currently active brand""" + return str(request._request.brand) def get_server_time(self, request: Request) -> datetime: """Current server time""" return now() + def get_embedded_outpost_disabled(self, request: Request) -> bool: + """Whether the embedded outpost is disabled""" + return CONFIG.get_bool("outposts.disable_embedded_outpost", False) + def get_embedded_outpost_host(self, request: Request) -> str: """Get the FQDN configured on the embedded outpost""" outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST) diff --git a/authentik/admin/api/tasks.py b/authentik/admin/api/tasks.py deleted file mode 100644 index 72714dad5c..0000000000 --- a/authentik/admin/api/tasks.py +++ /dev/null @@ -1,134 +0,0 @@ -"""Tasks API""" -from importlib import import_module - -from django.contrib import messages -from django.http.response import Http404 -from django.utils.translation import gettext_lazy as _ -from drf_spectacular.types import OpenApiTypes -from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema -from rest_framework.decorators import action -from rest_framework.fields import ( - CharField, - ChoiceField, - DateTimeField, - ListField, - SerializerMethodField, -) -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.viewsets import ViewSet -from structlog.stdlib import get_logger - -from authentik.api.decorators import permission_required -from authentik.core.api.utils import PassiveSerializer -from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus -from authentik.rbac.permissions import HasPermission - -LOGGER = get_logger() - - -class TaskSerializer(PassiveSerializer): - """Serialize TaskInfo and TaskResult""" - - task_name = CharField() - task_description = CharField() - task_finish_timestamp = DateTimeField(source="finish_time") - task_duration = SerializerMethodField() - - status = ChoiceField( - source="result.status.name", - choices=[(x.name, x.name) for x in TaskResultStatus], - ) - messages = ListField(source="result.messages") - - def get_task_duration(self, instance: TaskInfo) -> int: - """Get the duration a task took to run""" - return max(instance.finish_timestamp - instance.start_timestamp, 0) - - def to_representation(self, instance: TaskInfo): - """When a new version of authentik adds fields to TaskInfo, - the API will fail with an AttributeError, as the classes - are pickled in cache. In that case, just delete the info""" - try: - return super().to_representation(instance) - # pylint: disable=broad-except - except Exception: # pragma: no cover - if isinstance(self.instance, list): - for inst in self.instance: - inst.delete() - else: - self.instance.delete() - return {} - - -class TaskViewSet(ViewSet): - """Read-only view set that returns all background tasks""" - - permission_classes = [HasPermission("authentik_rbac.view_system_tasks")] - serializer_class = TaskSerializer - - @extend_schema( - responses={ - 200: TaskSerializer(many=False), - 404: OpenApiResponse(description="Task not found"), - }, - parameters=[ - OpenApiParameter( - "id", - type=OpenApiTypes.STR, - location=OpenApiParameter.PATH, - required=True, - ), - ], - ) - def retrieve(self, request: Request, pk=None) -> Response: - """Get a single system task""" - task = TaskInfo.by_name(pk) - if not task: - raise Http404 - return Response(TaskSerializer(task, many=False).data) - - @extend_schema(responses={200: TaskSerializer(many=True)}) - def list(self, request: Request) -> Response: - """List system tasks""" - tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name) - return Response(TaskSerializer(tasks, many=True).data) - - @permission_required(None, ["authentik_rbac.run_system_tasks"]) - @extend_schema( - request=OpenApiTypes.NONE, - responses={ - 204: OpenApiResponse(description="Task retried successfully"), - 404: OpenApiResponse(description="Task not found"), - 500: OpenApiResponse(description="Failed to retry task"), - }, - parameters=[ - OpenApiParameter( - "id", - type=OpenApiTypes.STR, - location=OpenApiParameter.PATH, - required=True, - ), - ], - ) - @action(detail=True, methods=["post"]) - def retry(self, request: Request, pk=None) -> Response: - """Retry task""" - task = TaskInfo.by_name(pk) - if not task: - raise Http404 - try: - task_module = import_module(task.task_call_module) - task_func = getattr(task_module, task.task_call_func) - LOGGER.debug("Running task", task=task_func) - task_func.delay(*task.task_call_args, **task.task_call_kwargs) - messages.success( - self.request, - _("Successfully re-scheduled Task %(name)s!" % {"name": task.task_name}), - ) - return Response(status=204) - except (ImportError, AttributeError): # pragma: no cover - LOGGER.warning("Failed to run task, remove state", task=task) - # if we get an import error, the module path has probably changed - task.delete() - return Response(status=500) diff --git a/authentik/admin/api/version.py b/authentik/admin/api/version.py index e0cc2c5b48..3985bef9b0 100644 --- a/authentik/admin/api/version.py +++ b/authentik/admin/api/version.py @@ -1,4 +1,5 @@ """authentik administration overview""" + from django.core.cache import cache from drf_spectacular.utils import extend_schema from packaging.version import parse @@ -9,7 +10,7 @@ from rest_framework.response import Response from rest_framework.views import APIView from authentik import __version__, get_build_hash -from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version +from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version from authentik.core.api.utils import PassiveSerializer @@ -18,6 +19,7 @@ class VersionSerializer(PassiveSerializer): version_current = SerializerMethodField() version_latest = SerializerMethodField() + version_latest_valid = SerializerMethodField() build_hash = SerializerMethodField() outdated = SerializerMethodField() @@ -37,6 +39,10 @@ class VersionSerializer(PassiveSerializer): return __version__ return version_in_cache + def get_version_latest_valid(self, _) -> bool: + """Check if latest version is valid""" + return cache.get(VERSION_CACHE_KEY) != VERSION_NULL + def get_outdated(self, instance) -> bool: """Check if we're running the latest version""" return parse(self.get_version_current(instance)) < parse(self.get_version_latest(instance)) diff --git a/authentik/admin/api/workers.py b/authentik/admin/api/workers.py index 3b5da05948..86578a50eb 100644 --- a/authentik/admin/api/workers.py +++ b/authentik/admin/api/workers.py @@ -1,4 +1,5 @@ """authentik administration overview""" + from django.conf import settings from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework.fields import IntegerField diff --git a/authentik/admin/apps.py b/authentik/admin/apps.py index a80e21d892..d151a6f953 100644 --- a/authentik/admin/apps.py +++ b/authentik/admin/apps.py @@ -1,4 +1,5 @@ """authentik admin app config""" + from prometheus_client import Gauge, Info from authentik.blueprints.apps import ManagedAppConfig @@ -14,7 +15,3 @@ class AuthentikAdminConfig(ManagedAppConfig): label = "authentik_admin" verbose_name = "authentik Admin" default = True - - def reconcile_load_admin_signals(self): - """Load admin signals""" - self.import_module("authentik.admin.signals") diff --git a/authentik/admin/settings.py b/authentik/admin/settings.py index 37b9d8bda1..7c5b561ff0 100644 --- a/authentik/admin/settings.py +++ b/authentik/admin/settings.py @@ -1,4 +1,5 @@ """authentik admin settings""" + from celery.schedules import crontab from authentik.lib.utils.time import fqdn_rand diff --git a/authentik/admin/signals.py b/authentik/admin/signals.py index f171fc74e6..6bd906972c 100644 --- a/authentik/admin/signals.py +++ b/authentik/admin/signals.py @@ -1,7 +1,7 @@ """admin signals""" + from django.dispatch import receiver -from authentik.admin.api.tasks import TaskInfo from authentik.admin.apps import GAUGE_WORKERS from authentik.root.celery import CELERY_APP from authentik.root.monitoring import monitoring_set @@ -12,10 +12,3 @@ def monitoring_set_workers(sender, **kwargs): """Set worker gauge""" count = len(CELERY_APP.control.ping(timeout=0.5)) GAUGE_WORKERS.set(count) - - -@receiver(monitoring_set) -def monitoring_set_tasks(sender, **kwargs): - """Set task gauges""" - for task in TaskInfo.all().values(): - task.update_metrics() diff --git a/authentik/admin/tasks.py b/authentik/admin/tasks.py index c5b0ebf613..8bafb3bef4 100644 --- a/authentik/admin/tasks.py +++ b/authentik/admin/tasks.py @@ -1,4 +1,5 @@ """authentik admin tasks""" + import re from django.core.cache import cache @@ -11,17 +12,13 @@ from structlog.stdlib import get_logger from authentik import __version__, get_build_hash from authentik.admin.apps import PROM_INFO from authentik.events.models import Event, EventAction, Notification -from authentik.events.monitored_tasks import ( - MonitoredTask, - TaskResult, - TaskResultStatus, - prefill_task, -) +from authentik.events.system_tasks import SystemTask, TaskStatus, prefill_task from authentik.lib.config import CONFIG from authentik.lib.utils.http import get_http_session from authentik.root.celery import CELERY_APP LOGGER = get_logger() +VERSION_NULL = "0.0.0" VERSION_CACHE_KEY = "authentik_latest_version" VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours # Chop of the first ^ because we want to search the entire string @@ -54,13 +51,13 @@ def clear_update_notifications(): notification.delete() -@CELERY_APP.task(bind=True, base=MonitoredTask) +@CELERY_APP.task(bind=True, base=SystemTask) @prefill_task -def update_latest_version(self: MonitoredTask): +def update_latest_version(self: SystemTask): """Update latest version info""" if CONFIG.get_bool("disable_update_check"): - cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) - self.set_status(TaskResult(TaskResultStatus.WARNING, messages=["Version check disabled."])) + cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT) + self.set_status(TaskStatus.WARNING, "Version check disabled.") return try: response = get_http_session().get( @@ -70,9 +67,7 @@ def update_latest_version(self: MonitoredTask): data = response.json() upstream_version = data.get("stable", {}).get("version") cache.set(VERSION_CACHE_KEY, upstream_version, VERSION_CACHE_TIMEOUT) - self.set_status( - TaskResult(TaskResultStatus.SUCCESSFUL, ["Successfully updated latest Version"]) - ) + self.set_status(TaskStatus.SUCCESSFUL, "Successfully updated latest Version") _set_prom_info() # Check if upstream version is newer than what we're running, # and if no event exists yet, create one. @@ -88,8 +83,8 @@ def update_latest_version(self: MonitoredTask): event_dict["message"] = f"Changelog: {match.group()}" Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save() except (RequestException, IndexError) as exc: - cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) - self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) + cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT) + self.set_error(exc) _set_prom_info() diff --git a/authentik/admin/tests/test_api.py b/authentik/admin/tests/test_api.py index 8fcbfa2869..8d7d5eb44a 100644 --- a/authentik/admin/tests/test_api.py +++ b/authentik/admin/tests/test_api.py @@ -1,4 +1,5 @@ """test admin api""" + from json import loads from django.test import TestCase @@ -7,8 +8,6 @@ from django.urls import reverse from authentik import __version__ from authentik.blueprints.tests import reconcile_app from authentik.core.models import Group, User -from authentik.core.tasks import clean_expired_models -from authentik.events.monitored_tasks import TaskResultStatus from authentik.lib.generators import generate_id @@ -23,53 +22,6 @@ class TestAdminAPI(TestCase): self.group.save() self.client.force_login(self.user) - def test_tasks(self): - """Test Task API""" - clean_expired_models.delay() - response = self.client.get(reverse("authentik_api:admin_system_tasks-list")) - self.assertEqual(response.status_code, 200) - body = loads(response.content) - self.assertTrue(any(task["task_name"] == "clean_expired_models" for task in body)) - - def test_tasks_single(self): - """Test Task API (read single)""" - clean_expired_models.delay() - response = self.client.get( - reverse( - "authentik_api:admin_system_tasks-detail", - kwargs={"pk": "clean_expired_models"}, - ) - ) - self.assertEqual(response.status_code, 200) - body = loads(response.content) - self.assertEqual(body["status"], TaskResultStatus.SUCCESSFUL.name) - self.assertEqual(body["task_name"], "clean_expired_models") - response = self.client.get( - reverse("authentik_api:admin_system_tasks-detail", kwargs={"pk": "qwerqwer"}) - ) - self.assertEqual(response.status_code, 404) - - def test_tasks_retry(self): - """Test Task API (retry)""" - clean_expired_models.delay() - response = self.client.post( - reverse( - "authentik_api:admin_system_tasks-retry", - kwargs={"pk": "clean_expired_models"}, - ) - ) - self.assertEqual(response.status_code, 204) - - def test_tasks_retry_404(self): - """Test Task API (retry, 404)""" - response = self.client.post( - reverse( - "authentik_api:admin_system_tasks-retry", - kwargs={"pk": "qwerqewrqrqewrqewr"}, - ) - ) - self.assertEqual(response.status_code, 404) - def test_version(self): """Test Version API""" response = self.client.get(reverse("authentik_api:admin_version")) diff --git a/authentik/admin/tests/test_tasks.py b/authentik/admin/tests/test_tasks.py index b6720623e5..0ea3457f61 100644 --- a/authentik/admin/tests/test_tasks.py +++ b/authentik/admin/tests/test_tasks.py @@ -1,4 +1,5 @@ """test admin tasks""" + from django.core.cache import cache from django.test import TestCase from requests_mock import Mocker diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index fec51f5f7d..2be8af5756 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -1,15 +1,14 @@ """API URLs""" + from django.urls import path from authentik.admin.api.meta import AppsViewSet, ModelViewSet from authentik.admin.api.metrics import AdministrationMetricsViewSet from authentik.admin.api.system import SystemView -from authentik.admin.api.tasks import TaskViewSet from authentik.admin.api.version import VersionView from authentik.admin.api.workers import WorkerView api_urlpatterns = [ - ("admin/system_tasks", TaskViewSet, "admin_system_tasks"), ("admin/apps", AppsViewSet, "apps"), ("admin/models", ModelViewSet, "models"), path( diff --git a/authentik/api/apps.py b/authentik/api/apps.py index 7b91d99a9e..8ae859a54d 100644 --- a/authentik/api/apps.py +++ b/authentik/api/apps.py @@ -10,26 +10,3 @@ class AuthentikAPIConfig(AppConfig): label = "authentik_api" mountpoint = "api/" verbose_name = "authentik API" - - def ready(self) -> None: - from drf_spectacular.extensions import OpenApiAuthenticationExtension - - from authentik.api.authentication import TokenAuthentication - - # Class is defined here as it needs to be created early enough that drf-spectacular will - # find it, but also won't cause any import issues - # pylint: disable=unused-variable - class TokenSchema(OpenApiAuthenticationExtension): - """Auth schema""" - - target_class = TokenAuthentication - name = "authentik" - - def get_security_definition(self, auto_schema): - """Auth schema""" - return { - "type": "apiKey", - "in": "header", - "name": "Authorization", - "scheme": "bearer", - } diff --git a/authentik/api/authentication.py b/authentik/api/authentication.py index 8aa402384f..ab4b67d731 100644 --- a/authentik/api/authentication.py +++ b/authentik/api/authentication.py @@ -1,8 +1,10 @@ """API Authentication""" + from hmac import compare_digest -from typing import Any, Optional +from typing import Any from django.conf import settings +from drf_spectacular.extensions import OpenApiAuthenticationExtension from rest_framework.authentication import BaseAuthentication, get_authorization_header from rest_framework.exceptions import AuthenticationFailed from rest_framework.request import Request @@ -16,7 +18,7 @@ from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API LOGGER = get_logger() -def validate_auth(header: bytes) -> Optional[str]: +def validate_auth(header: bytes) -> str | None: """Validate that the header is in a correct format, returns type and credentials""" auth_credentials = header.decode().strip() @@ -31,7 +33,7 @@ def validate_auth(header: bytes) -> Optional[str]: return auth_credentials -def bearer_auth(raw_header: bytes) -> Optional[User]: +def bearer_auth(raw_header: bytes) -> User | None: """raw_header in the Format of `Bearer ....`""" user = auth_user_lookup(raw_header) if not user: @@ -41,7 +43,7 @@ def bearer_auth(raw_header: bytes) -> Optional[User]: return user -def auth_user_lookup(raw_header: bytes) -> Optional[User]: +def auth_user_lookup(raw_header: bytes) -> User | None: """raw_header in the Format of `Bearer ....`""" from authentik.providers.oauth2.models import AccessToken @@ -74,7 +76,7 @@ def auth_user_lookup(raw_header: bytes) -> Optional[User]: raise AuthenticationFailed("Token invalid/expired") -def token_secret_key(value: str) -> Optional[User]: +def token_secret_key(value: str) -> User | None: """Check if the token is the secret key and return the service account for the managed outpost""" from authentik.outposts.apps import MANAGED_OUTPOST @@ -101,3 +103,14 @@ class TokenAuthentication(BaseAuthentication): return None return (user, None) # pragma: no cover + + +class TokenSchema(OpenApiAuthenticationExtension): + """Auth schema""" + + target_class = TokenAuthentication + name = "authentik" + + def get_security_definition(self, auto_schema): + """Auth schema""" + return {"type": "http", "scheme": "bearer"} diff --git a/authentik/api/authorization.py b/authentik/api/authorization.py index e3ae48e5c6..a79cb1803f 100644 --- a/authentik/api/authorization.py +++ b/authentik/api/authorization.py @@ -1,4 +1,5 @@ """API Authorization""" + from django.conf import settings from django.db.models import Model from django.db.models.query import QuerySet diff --git a/authentik/api/pagination.py b/authentik/api/pagination.py index 402dbac9b7..a49a6811b0 100644 --- a/authentik/api/pagination.py +++ b/authentik/api/pagination.py @@ -1,4 +1,5 @@ """Pagination which includes total pages and current page""" + from rest_framework import pagination from rest_framework.response import Response diff --git a/authentik/api/schema.py b/authentik/api/schema.py index e70caaf3e1..f95d575db4 100644 --- a/authentik/api/schema.py +++ b/authentik/api/schema.py @@ -1,4 +1,5 @@ """Error Response schema, from https://github.com/axnsan12/drf-yasg/issues/224""" + from django.utils.translation import gettext_lazy as _ from drf_spectacular.generators import SchemaGenerator from drf_spectacular.plumbing import ( @@ -11,6 +12,7 @@ from drf_spectacular.settings import spectacular_settings from drf_spectacular.types import OpenApiTypes from rest_framework.settings import api_settings +from authentik.api.apps import AuthentikAPIConfig from authentik.api.pagination import PAGINATION_COMPONENT_NAME, PAGINATION_SCHEMA @@ -100,3 +102,12 @@ def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs): comp = result["components"]["schemas"][component] comp["additionalProperties"] = {} return result + + +def preprocess_schema_exclude_non_api(endpoints, **kwargs): + """Filter out all API Views which are not mounted under /api""" + return [ + (path, path_regex, method, callback) + for path, path_regex, method, callback in endpoints + if path.startswith("/" + AuthentikAPIConfig.mountpoint) + ] diff --git a/authentik/api/templates/api/browser.html b/authentik/api/templates/api/browser.html index 2b4ed50fd1..a84ef3cb25 100644 --- a/authentik/api/templates/api/browser.html +++ b/authentik/api/templates/api/browser.html @@ -1,13 +1,13 @@ {% extends "base/skeleton.html" %} -{% load static %} +{% load authentik_core %} {% block title %} -API Browser - {{ tenant.branding_title }} +API Browser - {{ brand.branding_title }} {% endblock %} {% block head %} - +{% versioned_script "dist/standalone/api-browser/index-%v.js" %} {% endblock %} diff --git a/authentik/api/tests/test_auth.py b/authentik/api/tests/test_auth.py index cd23a18357..f449b88b60 100644 --- a/authentik/api/tests/test_auth.py +++ b/authentik/api/tests/test_auth.py @@ -1,4 +1,5 @@ """Test API Authentication""" + import json from base64 import b64encode @@ -12,6 +13,8 @@ from authentik.blueprints.tests import reconcile_app from authentik.core.models import Token, TokenIntents, User, UserTypes from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.lib.generators import generate_id +from authentik.outposts.apps import MANAGED_OUTPOST +from authentik.outposts.models import Outpost from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API from authentik.providers.oauth2.models import AccessToken, OAuth2Provider @@ -22,17 +25,17 @@ class TestAPIAuth(TestCase): def test_invalid_type(self): """Test invalid type""" with self.assertRaises(AuthenticationFailed): - bearer_auth("foo bar".encode()) + bearer_auth(b"foo bar") def test_invalid_empty(self): """Test invalid type""" - self.assertIsNone(bearer_auth("Bearer ".encode())) - self.assertIsNone(bearer_auth("".encode())) + self.assertIsNone(bearer_auth(b"Bearer ")) + self.assertIsNone(bearer_auth(b"")) def test_invalid_no_token(self): """Test invalid with no token""" with self.assertRaises(AuthenticationFailed): - auth = b64encode(":abc".encode()).decode() + auth = b64encode(b":abc").decode() self.assertIsNone(bearer_auth(f"Basic :{auth}".encode())) def test_bearer_valid(self): @@ -49,8 +52,12 @@ class TestAPIAuth(TestCase): with self.assertRaises(AuthenticationFailed): bearer_auth(f"Bearer {token.key}".encode()) - def test_managed_outpost(self): + @reconcile_app("authentik_outposts") + def test_managed_outpost_fail(self): """Test managed outpost""" + outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first() + outpost.user.delete() + outpost.delete() with self.assertRaises(AuthenticationFailed): bearer_auth(f"Bearer {settings.SECRET_KEY}".encode()) diff --git a/authentik/api/tests/test_config.py b/authentik/api/tests/test_config.py index 6f6f60f319..afee3fa177 100644 --- a/authentik/api/tests/test_config.py +++ b/authentik/api/tests/test_config.py @@ -1,4 +1,5 @@ """Test config API""" + from json import loads from django.urls import reverse diff --git a/authentik/api/tests/test_decorators.py b/authentik/api/tests/test_decorators.py deleted file mode 100644 index 2370683564..0000000000 --- a/authentik/api/tests/test_decorators.py +++ /dev/null @@ -1,34 +0,0 @@ -"""test decorators api""" -from django.urls import reverse -from guardian.shortcuts import assign_perm -from rest_framework.test import APITestCase - -from authentik.core.models import Application, User -from authentik.lib.generators import generate_id - - -class TestAPIDecorators(APITestCase): - """test decorators api""" - - def setUp(self) -> None: - super().setUp() - self.user = User.objects.create(username="test-user") - - def test_obj_perm_denied(self): - """Test object perm denied""" - self.client.force_login(self.user) - app = Application.objects.create(name=generate_id(), slug=generate_id()) - response = self.client.get( - reverse("authentik_api:application-metrics", kwargs={"slug": app.slug}) - ) - self.assertEqual(response.status_code, 403) - - def test_other_perm_denied(self): - """Test other perm denied""" - self.client.force_login(self.user) - app = Application.objects.create(name=generate_id(), slug=generate_id()) - assign_perm("authentik_core.view_application", self.user, app) - response = self.client.get( - reverse("authentik_api:application-metrics", kwargs={"slug": app.slug}) - ) - self.assertEqual(response.status_code, 403) diff --git a/authentik/api/tests/test_schema.py b/authentik/api/tests/test_schema.py index d86597d964..e33a31f9df 100644 --- a/authentik/api/tests/test_schema.py +++ b/authentik/api/tests/test_schema.py @@ -1,4 +1,5 @@ """Schema generation tests""" + from django.urls import reverse from rest_framework.test import APITestCase from yaml import safe_load diff --git a/authentik/api/tests/test_viewsets.py b/authentik/api/tests/test_viewsets.py index ac3d7da62d..da16e801c9 100644 --- a/authentik/api/tests/test_viewsets.py +++ b/authentik/api/tests/test_viewsets.py @@ -1,5 +1,6 @@ """authentik API Modelviewset tests""" -from typing import Callable + +from collections.abc import Callable from django.test import TestCase from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet @@ -25,6 +26,6 @@ def viewset_tester_factory(test_viewset: type[ModelViewSet]) -> Callable: for _, viewset, _ in router.registry: - if not issubclass(viewset, (ModelViewSet, ReadOnlyModelViewSet)): + if not issubclass(viewset, ModelViewSet | ReadOnlyModelViewSet): continue setattr(TestModelViewSets, f"test_viewset_{viewset.__name__}", viewset_tester_factory(viewset)) diff --git a/authentik/api/urls.py b/authentik/api/urls.py index 461fe5b94a..43b8827719 100644 --- a/authentik/api/urls.py +++ b/authentik/api/urls.py @@ -1,4 +1,5 @@ """authentik api urls""" + from django.urls import include, path from authentik.api.v3.urls import urlpatterns as v3_urls diff --git a/authentik/api/v3/config.py b/authentik/api/v3/config.py index 0defd1a5bd..151796afc4 100644 --- a/authentik/api/v3/config.py +++ b/authentik/api/v3/config.py @@ -1,4 +1,5 @@ """core Configs API""" + from pathlib import Path from django.conf import settings @@ -19,7 +20,7 @@ from rest_framework.response import Response from rest_framework.views import APIView from authentik.core.api.utils import PassiveSerializer -from authentik.events.geo import GEOIP_READER +from authentik.events.context_processors.base import get_context_processors from authentik.lib.config import CONFIG capabilities = Signal() @@ -30,6 +31,7 @@ class Capabilities(models.TextChoices): CAN_SAVE_MEDIA = "can_save_media" CAN_GEO_IP = "can_geo_ip" + CAN_ASN = "can_asn" CAN_IMPERSONATE = "can_impersonate" CAN_DEBUG = "can_debug" IS_ENTERPRISE = "is_enterprise" @@ -66,11 +68,16 @@ class ConfigView(APIView): """Get all capabilities this server instance supports""" caps = [] deb_test = settings.DEBUG or settings.TEST - if Path(settings.MEDIA_ROOT).is_mount() or deb_test: + if ( + CONFIG.get("storage.media.backend", "file") == "s3" + or Path(settings.STORAGES["default"]["OPTIONS"]["location"]).is_mount() + or deb_test + ): caps.append(Capabilities.CAN_SAVE_MEDIA) - if GEOIP_READER.enabled: - caps.append(Capabilities.CAN_GEO_IP) - if CONFIG.get_bool("impersonation"): + for processor in get_context_processors(): + if cap := processor.capability(): + caps.append(cap) + if self.request.tenant.impersonation: caps.append(Capabilities.CAN_IMPERSONATE) if settings.DEBUG: # pragma: no cover caps.append(Capabilities.CAN_DEBUG) diff --git a/authentik/api/v3/urls.py b/authentik/api/v3/urls.py index a7e610efac..735197dbd1 100644 --- a/authentik/api/v3/urls.py +++ b/authentik/api/v3/urls.py @@ -1,4 +1,5 @@ """api v3 urls""" + from importlib import import_module from django.urls import path @@ -32,7 +33,7 @@ for _authentik_app in get_apps(): app_name=_authentik_app.name, ) continue - urls: list = getattr(api_urls, "api_urlpatterns") + urls: list = api_urls.api_urlpatterns for url in urls: if isinstance(url, URLPattern): _other_urls.append(url) diff --git a/authentik/api/views.py b/authentik/api/views.py index 36193ed170..1e3f9eb242 100644 --- a/authentik/api/views.py +++ b/authentik/api/views.py @@ -1,4 +1,5 @@ """General API Views""" + from typing import Any from django.urls import reverse diff --git a/authentik/blueprints/api.py b/authentik/blueprints/api.py index 721eb5dcbe..993121e705 100644 --- a/authentik/blueprints/api.py +++ b/authentik/blueprints/api.py @@ -1,21 +1,22 @@ """Serializer mixin for managed models""" + from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework.decorators import action from rest_framework.exceptions import ValidationError -from rest_framework.fields import CharField, DateTimeField, JSONField +from rest_framework.fields import CharField, DateTimeField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ListSerializer, ModelSerializer from rest_framework.viewsets import ModelViewSet -from authentik.api.decorators import permission_required from authentik.blueprints.models import BlueprintInstance from authentik.blueprints.v1.importer import Importer from authentik.blueprints.v1.oci import OCI_PREFIX from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import PassiveSerializer +from authentik.core.api.utils import JSONDictField, PassiveSerializer +from authentik.rbac.decorators import permission_required class ManagedSerializer: @@ -28,7 +29,7 @@ class MetadataSerializer(PassiveSerializer): """Serializer for blueprint metadata""" name = CharField() - labels = JSONField() + labels = JSONDictField() class BlueprintInstanceSerializer(ModelSerializer): @@ -51,7 +52,9 @@ class BlueprintInstanceSerializer(ModelSerializer): valid, logs = Importer.from_string(content, context).validate() if not valid: text_logs = "\n".join([x["event"] for x in logs]) - raise ValidationError(_("Failed to validate blueprint: %(logs)s" % {"logs": text_logs})) + raise ValidationError( + _("Failed to validate blueprint: {logs}".format_map({"logs": text_logs})) + ) return content def validate(self, attrs: dict) -> dict: diff --git a/authentik/blueprints/apps.py b/authentik/blueprints/apps.py index 90df91c007..eeef482764 100644 --- a/authentik/blueprints/apps.py +++ b/authentik/blueprints/apps.py @@ -1,5 +1,6 @@ """authentik Blueprints app""" +from collections.abc import Callable from importlib import import_module from inspect import ismethod @@ -7,40 +8,100 @@ from django.apps import AppConfig from django.db import DatabaseError, InternalError, ProgrammingError from structlog.stdlib import BoundLogger, get_logger +from authentik.root.signals import startup + class ManagedAppConfig(AppConfig): """Basic reconciliation logic for apps""" - _logger: BoundLogger + logger: BoundLogger + + RECONCILE_GLOBAL_CATEGORY: str = "global" + RECONCILE_TENANT_CATEGORY: str = "tenant" def __init__(self, app_name: str, *args, **kwargs) -> None: super().__init__(app_name, *args, **kwargs) - self._logger = get_logger().bind(app_name=app_name) + self.logger = get_logger().bind(app_name=app_name) def ready(self) -> None: - self.reconcile() + self.import_related() + startup.connect(self._on_startup_callback, dispatch_uid=self.label) return super().ready() + def _on_startup_callback(self, sender, **_): + self._reconcile_global() + self._reconcile_tenant() + + def import_related(self): + """Automatically import related modules which rely on just being imported + to register themselves (mainly django signals and celery tasks)""" + + def import_relative(rel_module: str): + try: + module_name = f"{self.name}.{rel_module}" + import_module(module_name) + self.logger.info("Imported related module", module=module_name) + except ModuleNotFoundError: + pass + + import_relative("checks") + import_relative("tasks") + import_relative("signals") + def import_module(self, path: str): """Load module""" import_module(path) - def reconcile(self) -> None: - """reconcile ourselves""" - prefix = "reconcile_" + def _reconcile(self, prefix: str) -> None: for meth_name in dir(self): meth = getattr(self, meth_name) if not ismethod(meth): continue - if not meth_name.startswith(prefix): + category = getattr(meth, "_authentik_managed_reconcile", None) + if category != prefix: continue name = meth_name.replace(prefix, "") try: - self._logger.debug("Starting reconciler", name=name) + self.logger.debug("Starting reconciler", name=name) meth() - self._logger.debug("Successfully reconciled", name=name) + self.logger.debug("Successfully reconciled", name=name) except (DatabaseError, ProgrammingError, InternalError) as exc: - self._logger.debug("Failed to run reconcile", name=name, exc=exc) + self.logger.warning("Failed to run reconcile", name=name, exc=exc) + + @staticmethod + def reconcile_tenant(func: Callable): + """Mark a function to be called on startup (for each tenant)""" + func._authentik_managed_reconcile = ManagedAppConfig.RECONCILE_TENANT_CATEGORY + return func + + @staticmethod + def reconcile_global(func: Callable): + """Mark a function to be called on startup (globally)""" + func._authentik_managed_reconcile = ManagedAppConfig.RECONCILE_GLOBAL_CATEGORY + return func + + def _reconcile_tenant(self) -> None: + """reconcile ourselves for tenanted methods""" + from authentik.tenants.models import Tenant + + try: + tenants = list(Tenant.objects.filter(ready=True)) + except (DatabaseError, ProgrammingError, InternalError) as exc: + self.logger.debug("Failed to get tenants to run reconcile", exc=exc) + return + for tenant in tenants: + with tenant: + self._reconcile(self.RECONCILE_TENANT_CATEGORY) + + def _reconcile_global(self) -> None: + """ + reconcile ourselves for global methods. + Used for signals, tasks, etc. Database queries should not be made in here. + """ + from django_tenants.utils import get_public_schema_name, schema_context + + with schema_context(get_public_schema_name()): + self._reconcile(self.RECONCILE_GLOBAL_CATEGORY) class AuthentikBlueprintsConfig(ManagedAppConfig): @@ -51,11 +112,13 @@ class AuthentikBlueprintsConfig(ManagedAppConfig): verbose_name = "authentik Blueprints" default = True - def reconcile_load_blueprints_v1_tasks(self): + @ManagedAppConfig.reconcile_global + def load_blueprints_v1_tasks(self): """Load v1 tasks""" self.import_module("authentik.blueprints.v1.tasks") - def reconcile_blueprints_discovery(self): + @ManagedAppConfig.reconcile_tenant + def blueprints_discovery(self): """Run blueprint discovery""" from authentik.blueprints.v1.tasks import blueprints_discovery, clear_failed_blueprints diff --git a/authentik/blueprints/management/commands/apply_blueprint.py b/authentik/blueprints/management/commands/apply_blueprint.py index acc9ffbec5..698ff4b5c0 100644 --- a/authentik/blueprints/management/commands/apply_blueprint.py +++ b/authentik/blueprints/management/commands/apply_blueprint.py @@ -1,4 +1,5 @@ """Apply blueprint from commandline""" + from sys import exit as sys_exit from django.core.management.base import BaseCommand, no_translations @@ -6,6 +7,7 @@ from structlog.stdlib import get_logger from authentik.blueprints.models import BlueprintInstance from authentik.blueprints.v1.importer import Importer +from authentik.tenants.models import Tenant LOGGER = get_logger() @@ -16,14 +18,16 @@ class Command(BaseCommand): @no_translations def handle(self, *args, **options): """Apply all blueprints in order, abort when one fails to import""" - for blueprint_path in options.get("blueprints", []): - content = BlueprintInstance(path=blueprint_path).retrieve() - importer = Importer.from_string(content) - valid, _ = importer.validate() - if not valid: - self.stderr.write("blueprint invalid") - sys_exit(1) - importer.apply() + for tenant in Tenant.objects.filter(ready=True): + with tenant: + for blueprint_path in options.get("blueprints", []): + content = BlueprintInstance(path=blueprint_path).retrieve() + importer = Importer.from_string(content) + valid, _ = importer.validate() + if not valid: + self.stderr.write("blueprint invalid") + sys_exit(1) + importer.apply() def add_arguments(self, parser): parser.add_argument("blueprints", nargs="+", type=str) diff --git a/authentik/blueprints/management/commands/export_blueprint.py b/authentik/blueprints/management/commands/export_blueprint.py index d4b29304a6..c8371595e2 100644 --- a/authentik/blueprints/management/commands/export_blueprint.py +++ b/authentik/blueprints/management/commands/export_blueprint.py @@ -1,17 +1,19 @@ """Export blueprint of current authentik install""" -from django.core.management.base import BaseCommand, no_translations + +from django.core.management.base import no_translations from structlog.stdlib import get_logger from authentik.blueprints.v1.exporter import Exporter +from authentik.tenants.management import TenantCommand LOGGER = get_logger() -class Command(BaseCommand): +class Command(TenantCommand): """Export blueprint of current authentik install""" @no_translations - def handle(self, *args, **options): + def handle_per_tenant(self, *args, **options): """Export blueprint of current authentik install""" exporter = Exporter() self.stdout.write(exporter.export_to_string()) diff --git a/authentik/blueprints/management/commands/make_blueprint_schema.py b/authentik/blueprints/management/commands/make_blueprint_schema.py index f75b6ab0bc..6e482665d5 100644 --- a/authentik/blueprints/management/commands/make_blueprint_schema.py +++ b/authentik/blueprints/management/commands/make_blueprint_schema.py @@ -1,14 +1,17 @@ """Generate JSON Schema for blueprints""" + from json import dumps from typing import Any from django.core.management.base import BaseCommand, no_translations -from django.db.models import Model -from drf_jsonschema_serializer.convert import field_to_converter +from django.db.models import Model, fields +from drf_jsonschema_serializer.convert import converter, field_to_converter from rest_framework.fields import Field, JSONField, UUIDField +from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.serializers import Serializer from structlog.stdlib import get_logger +from authentik import __version__ from authentik.blueprints.v1.common import BlueprintEntryDesiredState from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, is_model_allowed from authentik.blueprints.v1.meta.registry import BaseMetaModel, registry @@ -17,6 +20,23 @@ from authentik.lib.models import SerializerModel LOGGER = get_logger() +@converter +class PrimaryKeyRelatedFieldConverter: + """Custom primary key field converter which is aware of non-integer based PKs + + This is not an exhaustive fix for other non-int PKs, however in authentik we either + use UUIDs or ints""" + + field_class = PrimaryKeyRelatedField + + def convert(self, field: PrimaryKeyRelatedField): + model: Model = field.queryset.model + pk_field = model._meta.pk + if isinstance(pk_field, fields.UUIDField): + return {"type": "string", "format": "uuid"} + return {"type": "integer"} + + class Command(BaseCommand): """Generate JSON Schema for blueprints""" @@ -28,7 +48,7 @@ class Command(BaseCommand): "$schema": "http://json-schema.org/draft-07/schema", "$id": "https://goauthentik.io/blueprints/schema.json", "type": "object", - "title": "authentik Blueprint schema", + "title": f"authentik {__version__} Blueprint schema", "required": ["version", "entries"], "properties": { "version": { diff --git a/authentik/blueprints/migrations/0001_initial.py b/authentik/blueprints/migrations/0001_initial.py index 8f6fb1a0f5..c8373214a3 100644 --- a/authentik/blueprints/migrations/0001_initial.py +++ b/authentik/blueprints/migrations/0001_initial.py @@ -14,7 +14,7 @@ from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_SYSTEM from authentik.lib.config import CONFIG -def check_blueprint_v1_file(BlueprintInstance: type, path: Path): +def check_blueprint_v1_file(BlueprintInstance: type, db_alias, path: Path): """Check if blueprint should be imported""" from authentik.blueprints.models import BlueprintInstanceStatus from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata @@ -29,7 +29,9 @@ def check_blueprint_v1_file(BlueprintInstance: type, path: Path): if version != 1: return blueprint_file.seek(0) - instance: BlueprintInstance = BlueprintInstance.objects.filter(path=path).first() + instance: BlueprintInstance = ( + BlueprintInstance.objects.using(db_alias).filter(path=path).first() + ) rel_path = path.relative_to(Path(CONFIG.get("blueprints_dir"))) meta = None if metadata: @@ -37,7 +39,7 @@ def check_blueprint_v1_file(BlueprintInstance: type, path: Path): if meta.labels.get(LABEL_AUTHENTIK_INSTANTIATE, "").lower() == "false": return if not instance: - instance = BlueprintInstance( + BlueprintInstance.objects.using(db_alias).create( name=meta.name if meta else str(rel_path), path=str(rel_path), context={}, @@ -47,7 +49,6 @@ def check_blueprint_v1_file(BlueprintInstance: type, path: Path): last_applied_hash="", metadata=metadata or {}, ) - instance.save() def migration_blueprint_import(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): @@ -56,7 +57,7 @@ def migration_blueprint_import(apps: Apps, schema_editor: BaseDatabaseSchemaEdit db_alias = schema_editor.connection.alias for file in glob(f"{CONFIG.get('blueprints_dir')}/**/*.yaml", recursive=True): - check_blueprint_v1_file(BlueprintInstance, Path(file)) + check_blueprint_v1_file(BlueprintInstance, db_alias, Path(file)) for blueprint in BlueprintInstance.objects.using(db_alias).all(): # If we already have flows (and we should always run before flow migrations) diff --git a/authentik/blueprints/models.py b/authentik/blueprints/models.py index 2551beacc2..a3abcba59f 100644 --- a/authentik/blueprints/models.py +++ b/authentik/blueprints/models.py @@ -1,4 +1,5 @@ """blueprint models""" + from pathlib import Path from uuid import uuid4 @@ -70,6 +71,19 @@ class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel): enabled = models.BooleanField(default=True) managed_models = ArrayField(models.TextField(), default=list) + class Meta: + verbose_name = _("Blueprint Instance") + verbose_name_plural = _("Blueprint Instances") + unique_together = ( + ( + "name", + "path", + ), + ) + + def __str__(self) -> str: + return f"Blueprint Instance {self.name}" + def retrieve_oci(self) -> str: """Get blueprint from an OCI registry""" client = BlueprintOCIClient(self.path.replace(OCI_PREFIX, "https://")) @@ -88,7 +102,7 @@ class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel): raise BlueprintRetrievalFailed("Invalid blueprint path") with full_path.open("r", encoding="utf-8") as _file: return _file.read() - except (IOError, OSError) as exc: + except OSError as exc: raise BlueprintRetrievalFailed(exc) from exc def retrieve(self) -> str: @@ -104,16 +118,3 @@ class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel): from authentik.blueprints.api import BlueprintInstanceSerializer return BlueprintInstanceSerializer - - def __str__(self) -> str: - return f"Blueprint Instance {self.name}" - - class Meta: - verbose_name = _("Blueprint Instance") - verbose_name_plural = _("Blueprint Instances") - unique_together = ( - ( - "name", - "path", - ), - ) diff --git a/authentik/blueprints/settings.py b/authentik/blueprints/settings.py index c9ce11057c..da0029d678 100644 --- a/authentik/blueprints/settings.py +++ b/authentik/blueprints/settings.py @@ -1,4 +1,5 @@ """blueprint Settings""" + from celery.schedules import crontab from authentik.lib.utils.time import fqdn_rand diff --git a/authentik/blueprints/tests/__init__.py b/authentik/blueprints/tests/__init__.py index 138e022423..69ded34098 100644 --- a/authentik/blueprints/tests/__init__.py +++ b/authentik/blueprints/tests/__init__.py @@ -1,6 +1,7 @@ """Blueprint helpers""" + +from collections.abc import Callable from functools import wraps -from typing import Callable from django.apps import apps @@ -38,7 +39,7 @@ def reconcile_app(app_name: str): def wrapper(*args, **kwargs): config = apps.get_app_config(app_name) if isinstance(config, ManagedAppConfig): - config.reconcile() + config._on_startup_callback(None) return func(*args, **kwargs) return wrapper diff --git a/authentik/blueprints/tests/test_models.py b/authentik/blueprints/tests/test_models.py index 2c04b64ef0..a2ceac828b 100644 --- a/authentik/blueprints/tests/test_models.py +++ b/authentik/blueprints/tests/test_models.py @@ -1,4 +1,5 @@ """authentik managed models tests""" + from django.test import TestCase from authentik.blueprints.models import BlueprintInstance, BlueprintRetrievalFailed diff --git a/authentik/blueprints/tests/test_oci.py b/authentik/blueprints/tests/test_oci.py index 963e3797ff..71b0d34807 100644 --- a/authentik/blueprints/tests/test_oci.py +++ b/authentik/blueprints/tests/test_oci.py @@ -1,4 +1,5 @@ """Test blueprints OCI""" + from django.test import TransactionTestCase from requests_mock import Mocker diff --git a/authentik/blueprints/tests/test_packaged.py b/authentik/blueprints/tests/test_packaged.py index c1e65e5270..443173bac2 100644 --- a/authentik/blueprints/tests/test_packaged.py +++ b/authentik/blueprints/tests/test_packaged.py @@ -1,22 +1,23 @@ """test packaged blueprints""" + +from collections.abc import Callable from pathlib import Path -from typing import Callable from django.test import TransactionTestCase from authentik.blueprints.models import BlueprintInstance from authentik.blueprints.tests import apply_blueprint from authentik.blueprints.v1.importer import Importer -from authentik.tenants.models import Tenant +from authentik.brands.models import Brand class TestPackaged(TransactionTestCase): """Empty class, test methods are added dynamically""" - @apply_blueprint("default/default-tenant.yaml") + @apply_blueprint("default/default-brand.yaml") def test_decorator_static(self): """Test @apply_blueprint decorator""" - self.assertTrue(Tenant.objects.filter(domain="authentik-default").exists()) + self.assertTrue(Brand.objects.filter(domain="authentik-default").exists()) def blueprint_tester(file_name: Path) -> Callable: diff --git a/authentik/blueprints/tests/test_serializer_models.py b/authentik/blueprints/tests/test_serializer_models.py index 78dc553483..a3ae6005aa 100644 --- a/authentik/blueprints/tests/test_serializer_models.py +++ b/authentik/blueprints/tests/test_serializer_models.py @@ -1,5 +1,6 @@ """authentik managed models tests""" -from typing import Callable, Type + +from collections.abc import Callable from django.apps import apps from django.test import TestCase @@ -13,7 +14,7 @@ class TestModels(TestCase): """Test Models""" -def serializer_tester_factory(test_model: Type[SerializerModel]) -> Callable: +def serializer_tester_factory(test_model: type[SerializerModel]) -> Callable: """Test serializer""" def tester(self: TestModels): diff --git a/authentik/blueprints/tests/test_v1.py b/authentik/blueprints/tests/test_v1.py index f7f53f780d..5f85e69477 100644 --- a/authentik/blueprints/tests/test_v1.py +++ b/authentik/blueprints/tests/test_v1.py @@ -1,4 +1,5 @@ """Test blueprints v1""" + from os import environ from django.test import TransactionTestCase diff --git a/authentik/blueprints/tests/test_v1_api.py b/authentik/blueprints/tests/test_v1_api.py index 6ee4bd7f6a..681260d690 100644 --- a/authentik/blueprints/tests/test_v1_api.py +++ b/authentik/blueprints/tests/test_v1_api.py @@ -1,4 +1,5 @@ """Test blueprints v1 api""" + from json import loads from tempfile import NamedTemporaryFile, mkdtemp diff --git a/authentik/blueprints/tests/test_v1_conditional_fields.py b/authentik/blueprints/tests/test_v1_conditional_fields.py index 4b7cb45915..d260e9599b 100644 --- a/authentik/blueprints/tests/test_v1_conditional_fields.py +++ b/authentik/blueprints/tests/test_v1_conditional_fields.py @@ -1,4 +1,5 @@ """Test blueprints v1""" + from django.test import TransactionTestCase from authentik.blueprints.v1.importer import Importer diff --git a/authentik/blueprints/tests/test_v1_conditions.py b/authentik/blueprints/tests/test_v1_conditions.py index dd633a22a6..8dd79563e1 100644 --- a/authentik/blueprints/tests/test_v1_conditions.py +++ b/authentik/blueprints/tests/test_v1_conditions.py @@ -1,4 +1,5 @@ """Test blueprints v1""" + from django.test import TransactionTestCase from authentik.blueprints.v1.importer import Importer diff --git a/authentik/blueprints/tests/test_v1_state.py b/authentik/blueprints/tests/test_v1_state.py index ad12c239c8..96a2d0f602 100644 --- a/authentik/blueprints/tests/test_v1_state.py +++ b/authentik/blueprints/tests/test_v1_state.py @@ -1,4 +1,5 @@ """Test blueprints v1""" + from django.test import TransactionTestCase from authentik.blueprints.v1.importer import Importer diff --git a/authentik/blueprints/tests/test_v1_tasks.py b/authentik/blueprints/tests/test_v1_tasks.py index 913431d66b..b1d201419d 100644 --- a/authentik/blueprints/tests/test_v1_tasks.py +++ b/authentik/blueprints/tests/test_v1_tasks.py @@ -1,4 +1,5 @@ """Test blueprints v1 tasks""" + from hashlib import sha512 from tempfile import NamedTemporaryFile, mkdtemp @@ -53,7 +54,7 @@ class TestBlueprintsV1Tasks(TransactionTestCase): file.seek(0) file_hash = sha512(file.read().encode()).hexdigest() file.flush() - blueprints_discovery() # pylint: disable=no-value-for-parameter + blueprints_discovery() instance = BlueprintInstance.objects.filter(name=blueprint_id).first() self.assertEqual(instance.last_applied_hash, file_hash) self.assertEqual( @@ -81,7 +82,7 @@ class TestBlueprintsV1Tasks(TransactionTestCase): ) ) file.flush() - blueprints_discovery() # pylint: disable=no-value-for-parameter + blueprints_discovery() blueprint = BlueprintInstance.objects.filter(name="foo").first() self.assertEqual( blueprint.last_applied_hash, @@ -106,7 +107,7 @@ class TestBlueprintsV1Tasks(TransactionTestCase): ) ) file.flush() - blueprints_discovery() # pylint: disable=no-value-for-parameter + blueprints_discovery() blueprint.refresh_from_db() self.assertEqual( blueprint.last_applied_hash, @@ -148,7 +149,7 @@ class TestBlueprintsV1Tasks(TransactionTestCase): instance.status, BlueprintInstanceStatus.UNKNOWN, ) - apply_blueprint(instance.pk) # pylint: disable=no-value-for-parameter + apply_blueprint(instance.pk) instance.refresh_from_db() self.assertEqual(instance.last_applied_hash, "") self.assertEqual( diff --git a/authentik/blueprints/urls.py b/authentik/blueprints/urls.py index 7f18c0094c..bea95598ea 100644 --- a/authentik/blueprints/urls.py +++ b/authentik/blueprints/urls.py @@ -1,4 +1,5 @@ """API URLs""" + from authentik.blueprints.api import BlueprintInstanceViewSet api_urlpatterns = [ diff --git a/authentik/blueprints/v1/common.py b/authentik/blueprints/v1/common.py index 997fecc6f8..21b923f2e7 100644 --- a/authentik/blueprints/v1/common.py +++ b/authentik/blueprints/v1/common.py @@ -1,12 +1,14 @@ """transfer common classes""" + from collections import OrderedDict +from collections.abc import Iterable, Mapping from copy import copy from dataclasses import asdict, dataclass, field, is_dataclass from enum import Enum from functools import reduce from operator import ixor from os import getenv -from typing import Any, Iterable, Literal, Mapping, Optional, Union +from typing import Any, Literal, Union from uuid import UUID from deepmerge import always_merger @@ -44,7 +46,7 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]: class BlueprintEntryState: """State of a single instance""" - instance: Optional[Model] = None + instance: Model | None = None class BlueprintEntryDesiredState(Enum): @@ -66,14 +68,14 @@ class BlueprintEntry: ) conditions: list[Any] = field(default_factory=list) identifiers: dict[str, Any] = field(default_factory=dict) - attrs: Optional[dict[str, Any]] = field(default_factory=dict) + attrs: dict[str, Any] | None = field(default_factory=dict) - id: Optional[str] = None + id: str | None = None _state: BlueprintEntryState = field(default_factory=BlueprintEntryState) def __post_init__(self, *args, **kwargs) -> None: - self.__tag_contexts: list["YAMLTagContext"] = [] + self.__tag_contexts: list[YAMLTagContext] = [] @staticmethod def from_model(model: SerializerModel, *extra_identifier_names: str) -> "BlueprintEntry": @@ -91,10 +93,10 @@ class BlueprintEntry: attrs=all_attrs, ) - def _get_tag_context( + def get_tag_context( self, depth: int = 0, - context_tag_type: Optional[type["YAMLTagContext"] | tuple["YAMLTagContext", ...]] = None, + context_tag_type: type["YAMLTagContext"] | tuple["YAMLTagContext", ...] | None = None, ) -> "YAMLTagContext": """Get a YAMLTagContext object located at a certain depth in the tag tree""" if depth < 0: @@ -107,8 +109,8 @@ class BlueprintEntry: try: return contexts[-(depth + 1)] - except IndexError: - raise ValueError(f"invalid depth: {depth}. Max depth: {len(contexts) - 1}") + except IndexError as exc: + raise ValueError(f"invalid depth: {depth}. Max depth: {len(contexts) - 1}") from exc def tag_resolver(self, value: Any, blueprint: "Blueprint") -> Any: """Check if we have any special tags that need handling""" @@ -169,7 +171,7 @@ class Blueprint: entries: list[BlueprintEntry] = field(default_factory=list) context: dict = field(default_factory=dict) - metadata: Optional[BlueprintMetadata] = field(default=None) + metadata: BlueprintMetadata | None = field(default=None) class YAMLTag: @@ -217,7 +219,7 @@ class Env(YAMLTag): """Lookup environment variable with optional default""" key: str - default: Optional[Any] + default: Any | None def __init__(self, loader: "BlueprintLoader", node: ScalarNode | SequenceNode) -> None: super().__init__() @@ -236,7 +238,7 @@ class Context(YAMLTag): """Lookup key from instance context""" key: str - default: Optional[Any] + default: Any | None def __init__(self, loader: "BlueprintLoader", node: ScalarNode | SequenceNode) -> None: super().__init__() @@ -280,7 +282,7 @@ class Format(YAMLTag): try: return self.format_string % tuple(args) except TypeError as exc: - raise EntryInvalidError.from_entry(exc, entry) + raise EntryInvalidError.from_entry(exc, entry) from exc class Find(YAMLTag): @@ -365,7 +367,7 @@ class Condition(YAMLTag): comparator = self._COMPARATORS[self.mode.upper()] return comparator(tuple(bool(x) for x in args)) except (TypeError, KeyError) as exc: - raise EntryInvalidError.from_entry(exc, entry) + raise EntryInvalidError.from_entry(exc, entry) from exc class If(YAMLTag): @@ -397,7 +399,7 @@ class If(YAMLTag): blueprint, ) except TypeError as exc: - raise EntryInvalidError.from_entry(exc, entry) + raise EntryInvalidError.from_entry(exc, entry) from exc class Enumerate(YAMLTag, YAMLTagContext): @@ -411,9 +413,7 @@ class Enumerate(YAMLTag, YAMLTagContext): "SEQ": (list, lambda a, b: [*a, b]), "MAP": ( dict, - lambda a, b: always_merger.merge( - a, {b[0]: b[1]} if isinstance(b, (tuple, list)) else b - ), + lambda a, b: always_merger.merge(a, {b[0]: b[1]} if isinstance(b, tuple | list) else b), ), } @@ -455,7 +455,7 @@ class Enumerate(YAMLTag, YAMLTagContext): try: output_class, add_fn = self._OUTPUT_BODIES[self.output_body.upper()] except KeyError as exc: - raise EntryInvalidError.from_entry(exc, entry) + raise EntryInvalidError.from_entry(exc, entry) from exc result = output_class() @@ -483,13 +483,13 @@ class EnumeratedItem(YAMLTag): _SUPPORTED_CONTEXT_TAGS = (Enumerate,) - def __init__(self, loader: "BlueprintLoader", node: ScalarNode) -> None: + def __init__(self, _loader: "BlueprintLoader", node: ScalarNode) -> None: super().__init__() self.depth = int(node.value) def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any: try: - context_tag: Enumerate = entry._get_tag_context( + context_tag: Enumerate = entry.get_tag_context( depth=self.depth, context_tag_type=EnumeratedItem._SUPPORTED_CONTEXT_TAGS, ) @@ -499,9 +499,11 @@ class EnumeratedItem(YAMLTag): f"{self.__class__.__name__} tags are only usable " f"inside an {Enumerate.__name__} tag", entry, - ) + ) from exc - raise EntryInvalidError.from_entry(f"{self.__class__.__name__} tag: {exc}", entry) + raise EntryInvalidError.from_entry( + f"{self.__class__.__name__} tag: {exc}", entry + ) from exc return context_tag.get_context(entry, blueprint) @@ -514,8 +516,8 @@ class Index(EnumeratedItem): try: return context[0] - except IndexError: # pragma: no cover - raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) + except IndexError as exc: # pragma: no cover + raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) from exc class Value(EnumeratedItem): @@ -526,8 +528,8 @@ class Value(EnumeratedItem): try: return context[1] - except IndexError: # pragma: no cover - raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) + except IndexError as exc: # pragma: no cover + raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) from exc class BlueprintDumper(SafeDumper): @@ -554,7 +556,11 @@ class BlueprintDumper(SafeDumper): def factory(items): final_dict = dict(items) + # Remove internal state variables final_dict.pop("_state", None) + # Future-proof to only remove the ID if we don't set a value + if "id" in final_dict and final_dict.get("id") is None: + final_dict.pop("id") return final_dict data = asdict(data, dict_factory=factory) @@ -581,13 +587,13 @@ class BlueprintLoader(SafeLoader): class EntryInvalidError(SentryIgnoredException): """Error raised when an entry is invalid""" - entry_model: Optional[str] - entry_id: Optional[str] - validation_error: Optional[ValidationError] - serializer: Optional[Serializer] = None + entry_model: str | None + entry_id: str | None + validation_error: ValidationError | None + serializer: Serializer | None = None def __init__( - self, *args: object, validation_error: Optional[ValidationError] = None, **kwargs + self, *args: object, validation_error: ValidationError | None = None, **kwargs ) -> None: super().__init__(*args) self.entry_model = None diff --git a/authentik/blueprints/v1/exporter.py b/authentik/blueprints/v1/exporter.py index b3f6ca0422..89ed20be04 100644 --- a/authentik/blueprints/v1/exporter.py +++ b/authentik/blueprints/v1/exporter.py @@ -1,5 +1,6 @@ """Blueprint exporter""" -from typing import Iterable + +from collections.abc import Iterable from uuid import UUID from django.apps import apps @@ -7,7 +8,6 @@ from django.contrib.auth import get_user_model from django.db.models import Model, Q, QuerySet from django.utils.timezone import now from django.utils.translation import gettext as _ -from guardian.shortcuts import get_anonymous_user from yaml import dump from authentik.blueprints.v1.common import ( @@ -48,7 +48,7 @@ class Exporter: """Return a queryset for `model`. Can be used to filter some objects on some models""" if model == get_user_model(): - return model.objects.exclude(pk=get_anonymous_user().pk) + return model.objects.exclude_anonymous() return model.objects.all() def _pre_export(self, blueprint: Blueprint): @@ -59,7 +59,7 @@ class Exporter: blueprint = Blueprint() self._pre_export(blueprint) blueprint.metadata = BlueprintMetadata( - name=_("authentik Export - %(date)s" % {"date": str(now())}), + name=_("authentik Export - {date}".format_map({"date": str(now())})), labels={ LABEL_AUTHENTIK_GENERATED: "true", }, @@ -74,7 +74,7 @@ class Exporter: class FlowExporter(Exporter): - """Exporter customised to only return objects related to `flow`""" + """Exporter customized to only return objects related to `flow`""" flow: Flow with_policies: bool diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py index eb49429589..92f008901f 100644 --- a/authentik/blueprints/v1/importer.py +++ b/authentik/blueprints/v1/importer.py @@ -1,22 +1,24 @@ """Blueprint importer""" + from contextlib import contextmanager from copy import deepcopy -from typing import Any, Optional +from typing import Any from dacite.config import Config from dacite.core import from_dict from dacite.exceptions import DaciteError from deepmerge import always_merger +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldError from django.db.models import Model from django.db.models.query_utils import Q from django.db.transaction import atomic from django.db.utils import IntegrityError +from guardian.models import UserObjectPermission from rest_framework.exceptions import ValidationError from rest_framework.serializers import BaseSerializer, Serializer from structlog.stdlib import BoundLogger, get_logger -from structlog.testing import capture_logs -from structlog.types import EventDict from yaml import load from authentik.blueprints.v1.common import ( @@ -35,14 +37,31 @@ from authentik.core.models import ( Source, UserSourceConnection, ) +from authentik.enterprise.license import LicenseKey from authentik.enterprise.models import LicenseUsage +from authentik.enterprise.providers.google_workspace.models import ( + GoogleWorkspaceProviderGroup, + GoogleWorkspaceProviderUser, +) +from authentik.enterprise.providers.microsoft_entra.models import ( + MicrosoftEntraProviderGroup, + MicrosoftEntraProviderUser, +) +from authentik.enterprise.providers.rac.models import ConnectionToken +from authentik.events.logs import LogEvent, capture_logs +from authentik.events.models import SystemTask from authentik.events.utils import cleanse_dict from authentik.flows.models import FlowToken, Stage from authentik.lib.models import SerializerModel from authentik.lib.sentry import SentryIgnoredException from authentik.outposts.models import OutpostServiceConnection from authentik.policies.models import Policy, PolicyBindingModel -from authentik.providers.scim.models import SCIMGroup, SCIMUser +from authentik.policies.reputation.models import Reputation +from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken +from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser +from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser +from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType +from authentik.tenants.models import Tenant # Context set when the serializer is created in a blueprint context # Update website/developer-docs/blueprints/v1/models.md when used @@ -52,13 +71,17 @@ SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry" def excluded_models() -> list[type[Model]]: """Return a list of all excluded models that shouldn't be exposed via API or other means (internal only, base classes, non-used objects, etc)""" - # pylint: disable=imported-auth-user + from django.contrib.auth.models import Group as DjangoGroup from django.contrib.auth.models import User as DjangoUser return ( + # Django only classes DjangoUser, DjangoGroup, + ContentType, + Permission, + UserObjectPermission, # Base classes Provider, Source, @@ -71,16 +94,31 @@ def excluded_models() -> list[type[Model]]: # Classes that have other dependencies AuthenticatedSession, # Classes which are only internally managed + # FIXME: these shouldn't need to be explicitly listed, but rather based off of a mixin FlowToken, LicenseUsage, - SCIMGroup, - SCIMUser, + SCIMProviderGroup, + SCIMProviderUser, + Tenant, + SystemTask, + ConnectionToken, + AuthorizationCode, + AccessToken, + RefreshToken, + Reputation, + WebAuthnDeviceType, + SCIMSourceUser, + SCIMSourceGroup, + GoogleWorkspaceProviderUser, + GoogleWorkspaceProviderGroup, + MicrosoftEntraProviderUser, + MicrosoftEntraProviderGroup, ) def is_model_allowed(model: type[Model]) -> bool: """Check if model is allowed""" - return model not in excluded_models() and issubclass(model, (SerializerModel, BaseMetaModel)) + return model not in excluded_models() and issubclass(model, SerializerModel | BaseMetaModel) class DoRollback(SentryIgnoredException): @@ -104,16 +142,20 @@ class Importer: logger: BoundLogger _import: Blueprint - def __init__(self, blueprint: Blueprint, context: Optional[dict] = None): + def __init__(self, blueprint: Blueprint, context: dict | None = None): self.__pk_map: dict[Any, Model] = {} self._import = blueprint self.logger = get_logger() - ctx = {} + ctx = self.default_context() always_merger.merge(ctx, self._import.context) if context: always_merger.merge(ctx, context) self._import.context = ctx + def default_context(self): + """Default context""" + return {"goauthentik.io/enterprise/licensed": LicenseKey.get_total().is_valid()} + @staticmethod def from_string(yaml_input: str, context: dict | None = None) -> "Importer": """Parse YAML string and create blueprint importer from it""" @@ -136,14 +178,14 @@ class Importer: def updater(value) -> Any: if value in self.__pk_map: - self.logger.debug("updating reference in entry", value=value) + self.logger.debug("Updating reference in entry", value=value) return self.__pk_map[value] return value for key, value in attrs.items(): try: if isinstance(value, dict): - for idx, _inner_key in enumerate(value): + for _, _inner_key in enumerate(value): value[_inner_key] = updater(value[_inner_key]) elif isinstance(value, list): for idx, _inner_value in enumerate(value): @@ -172,8 +214,7 @@ class Importer: return main_query | sub_query - # pylint: disable-msg=too-many-locals - def _validate_single(self, entry: BlueprintEntry) -> Optional[BaseSerializer]: + def _validate_single(self, entry: BlueprintEntry) -> BaseSerializer | None: """Validate a single entry""" if not entry.check_all_conditions_match(self._import): self.logger.debug("One or more conditions of this entry are not fulfilled, skipping") @@ -226,7 +267,7 @@ class Importer: model_instance = existing_models.first() if not isinstance(model(), BaseMetaModel) and model_instance: self.logger.debug( - "initialise serializer with instance", + "Initialise serializer with instance", model=model, instance=model_instance, pk=model_instance.pk, @@ -236,14 +277,14 @@ class Importer: elif model_instance and entry.state == BlueprintEntryDesiredState.MUST_CREATED: raise EntryInvalidError.from_entry( ( - f"state is set to {BlueprintEntryDesiredState.MUST_CREATED} " + f"State is set to {BlueprintEntryDesiredState.MUST_CREATED} " "and object exists already", ), entry, ) else: self.logger.debug( - "initialised new serializer instance", + "Initialised new serializer instance", model=model, **cleanse_dict(updated_identifiers), ) @@ -300,7 +341,7 @@ class Importer: model: type[SerializerModel] = registry.get_model(model_app_label, model_name) except LookupError: self.logger.warning( - "app or model does not exist", app=model_app_label, model=model_name + "App or Model does not exist", app=model_app_label, model=model_name ) return False # Validate each single entry @@ -312,7 +353,7 @@ class Importer: if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT: serializer = exc.serializer else: - self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc) + self.logger.warning(f"Entry invalid: {exc}", entry=entry, error=exc) if raise_errors: raise exc return False @@ -332,27 +373,27 @@ class Importer: and state == BlueprintEntryDesiredState.CREATED ): self.logger.debug( - "instance exists, skipping", + "Instance exists, skipping", model=model, instance=instance, pk=instance.pk, ) else: instance = serializer.save() - self.logger.debug("updated model", model=instance) + self.logger.debug("Updated model", model=instance) if "pk" in entry.identifiers: self.__pk_map[entry.identifiers["pk"]] = instance.pk entry._state = BlueprintEntryState(instance) elif state == BlueprintEntryDesiredState.ABSENT: - instance: Optional[Model] = serializer.instance + instance: Model | None = serializer.instance if instance.pk: instance.delete() - self.logger.debug("deleted model", mode=instance) + self.logger.debug("Deleted model", mode=instance) continue - self.logger.debug("entry to delete with no instance, skipping") + self.logger.debug("Entry to delete with no instance, skipping") return True - def validate(self, raise_validation_errors=False) -> tuple[bool, list[EventDict]]: + def validate(self, raise_validation_errors=False) -> tuple[bool, list[LogEvent]]: """Validate loaded blueprint export, ensure all models are allowed and serializers have no errors""" self.logger.debug("Starting blueprint import validation") @@ -366,9 +407,7 @@ class Importer: ): successful = self._apply_models(raise_errors=raise_validation_errors) if not successful: - self.logger.debug("Blueprint validation failed") - for log in logs: - getattr(self.logger, log.get("log_level"))(**log) + self.logger.warning("Blueprint validation failed") self.logger.debug("Finished blueprint import validation") self._import = orig_import return successful, logs diff --git a/authentik/blueprints/v1/meta/apply_blueprint.py b/authentik/blueprints/v1/meta/apply_blueprint.py index 5946342a33..abd593c045 100644 --- a/authentik/blueprints/v1/meta/apply_blueprint.py +++ b/authentik/blueprints/v1/meta/apply_blueprint.py @@ -1,12 +1,13 @@ """Apply Blueprint meta model""" + from typing import TYPE_CHECKING from rest_framework.exceptions import ValidationError -from rest_framework.fields import BooleanField, JSONField +from rest_framework.fields import BooleanField from structlog.stdlib import get_logger from authentik.blueprints.v1.meta.registry import BaseMetaModel, MetaResult, registry -from authentik.core.api.utils import PassiveSerializer, is_dict +from authentik.core.api.utils import JSONDictField, PassiveSerializer if TYPE_CHECKING: from authentik.blueprints.models import BlueprintInstance @@ -17,7 +18,7 @@ LOGGER = get_logger() class ApplyBlueprintMetaSerializer(PassiveSerializer): """Serializer for meta apply blueprint model""" - identifiers = JSONField(validators=[is_dict]) + identifiers = JSONDictField() required = BooleanField(default=True) # We cannot override `instance` as that will confuse rest_framework @@ -42,7 +43,7 @@ class ApplyBlueprintMetaSerializer(PassiveSerializer): LOGGER.info("Blueprint does not exist, but not required") return MetaResult() LOGGER.debug("Applying blueprint from meta model", blueprint=self.blueprint_instance) - # pylint: disable=no-value-for-parameter + apply_blueprint(str(self.blueprint_instance.pk)) return MetaResult() diff --git a/authentik/blueprints/v1/meta/registry.py b/authentik/blueprints/v1/meta/registry.py index 70e409f9c4..7d64815914 100644 --- a/authentik/blueprints/v1/meta/registry.py +++ b/authentik/blueprints/v1/meta/registry.py @@ -1,4 +1,5 @@ """Base models""" + from django.apps import apps from django.db.models import Model from rest_framework.serializers import Serializer @@ -7,15 +8,15 @@ from rest_framework.serializers import Serializer class BaseMetaModel(Model): """Base models""" + class Meta: + abstract = True + @staticmethod def serializer() -> Serializer: """Serializer similar to SerializerModel, but as a static method since this is an abstract model""" raise NotImplementedError - class Meta: - abstract = True - class MetaResult: """Result returned by Meta Models' serializers. Empty class but we can't return none as diff --git a/authentik/blueprints/v1/oci.py b/authentik/blueprints/v1/oci.py index d02f3fa2f0..6a22c4037f 100644 --- a/authentik/blueprints/v1/oci.py +++ b/authentik/blueprints/v1/oci.py @@ -1,4 +1,5 @@ """OCI Client""" + from typing import Any from urllib.parse import ParseResult, urlparse diff --git a/authentik/blueprints/v1/tasks.py b/authentik/blueprints/v1/tasks.py index 686e4747c9..c8b72a5dc2 100644 --- a/authentik/blueprints/v1/tasks.py +++ b/authentik/blueprints/v1/tasks.py @@ -1,8 +1,9 @@ """v1 blueprints tasks""" + from dataclasses import asdict, dataclass, field from hashlib import sha512 from pathlib import Path -from typing import Optional +from sys import platform from dacite.core import from_dict from django.db import DatabaseError, InternalError, ProgrammingError @@ -29,15 +30,13 @@ from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata, E from authentik.blueprints.v1.importer import Importer from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE from authentik.blueprints.v1.oci import OCI_PREFIX -from authentik.events.monitored_tasks import ( - MonitoredTask, - TaskResult, - TaskResultStatus, - prefill_task, -) +from authentik.events.logs import capture_logs +from authentik.events.models import TaskStatus +from authentik.events.system_tasks import SystemTask, prefill_task from authentik.events.utils import sanitize_dict from authentik.lib.config import CONFIG from authentik.root.celery import CELERY_APP +from authentik.tenants.models import Tenant LOGGER = get_logger() _file_watcher_started = False @@ -51,18 +50,23 @@ class BlueprintFile: version: int hash: str last_m: int - meta: Optional[BlueprintMetadata] = field(default=None) + meta: BlueprintMetadata | None = field(default=None) def start_blueprint_watcher(): """Start blueprint watcher, if it's not running already.""" # This function might be called twice since it's called on celery startup - # pylint: disable=global-statement - global _file_watcher_started + + global _file_watcher_started # noqa: PLW0603 if _file_watcher_started: return observer = Observer() - observer.schedule(BlueprintEventHandler(), CONFIG.get("blueprints_dir"), recursive=True) + kwargs = {} + if platform.startswith("linux"): + kwargs["event_filter"] = (FileCreatedEvent, FileModifiedEvent) + observer.schedule( + BlueprintEventHandler(), CONFIG.get("blueprints_dir"), recursive=True, **kwargs + ) observer.start() _file_watcher_started = True @@ -70,21 +74,36 @@ def start_blueprint_watcher(): class BlueprintEventHandler(FileSystemEventHandler): """Event handler for blueprint events""" - def on_any_event(self, event: FileSystemEvent): - if not isinstance(event, (FileCreatedEvent, FileModifiedEvent)): - return + # We only ever get creation and modification events. + # See the creation of the Observer instance above for the event filtering. + + # Even though we filter to only get file events, we might still get + # directory events as some implementations such as inotify do not support + # filtering on file/directory. + + def dispatch(self, event: FileSystemEvent) -> None: + """Call specific event handler method. Ignores directory changes.""" if event.is_directory: - return + return None + return super().dispatch(event) + + def on_created(self, event: FileSystemEvent): + """Process file creation""" + LOGGER.debug("new blueprint file created, starting discovery") + for tenant in Tenant.objects.filter(ready=True): + with tenant: + blueprints_discovery.delay() + + def on_modified(self, event: FileSystemEvent): + """Process file modification""" + path = Path(event.src_path) root = Path(CONFIG.get("blueprints_dir")).absolute() - path = Path(event.src_path).absolute() rel_path = str(path.relative_to(root)) - if isinstance(event, FileCreatedEvent): - LOGGER.debug("new blueprint file created, starting discovery", path=rel_path) - blueprints_discovery.delay(rel_path) - if isinstance(event, FileModifiedEvent): - for instance in BlueprintInstance.objects.filter(path=rel_path, enabled=True): - LOGGER.debug("modified blueprint file, starting apply", instance=instance) - apply_blueprint.delay(instance.pk.hex) + for tenant in Tenant.objects.filter(ready=True): + with tenant: + for instance in BlueprintInstance.objects.filter(path=rel_path, enabled=True): + LOGGER.debug("modified blueprint file, starting apply", instance=instance) + apply_blueprint.delay(instance.pk.hex) @CELERY_APP.task( @@ -107,7 +126,7 @@ def blueprints_find() -> list[BlueprintFile]: # Check if any part in the path starts with a dot and assume a hidden file if any(part for part in path.parts if part.startswith(".")): continue - with open(path, "r", encoding="utf-8") as blueprint_file: + with open(path, encoding="utf-8") as blueprint_file: try: raw_blueprint = load(blueprint_file.read(), BlueprintLoader) except YAMLError as exc: @@ -128,10 +147,10 @@ def blueprints_find() -> list[BlueprintFile]: @CELERY_APP.task( - throws=(DatabaseError, ProgrammingError, InternalError), base=MonitoredTask, bind=True + throws=(DatabaseError, ProgrammingError, InternalError), base=SystemTask, bind=True ) @prefill_task -def blueprints_discovery(self: MonitoredTask, path: Optional[str] = None): +def blueprints_discovery(self: SystemTask, path: str | None = None): """Find blueprints and check if they need to be created in the database""" count = 0 for blueprint in blueprints_find(): @@ -140,10 +159,7 @@ def blueprints_discovery(self: MonitoredTask, path: Optional[str] = None): check_blueprint_v1_file(blueprint) count += 1 self.set_status( - TaskResult( - TaskResultStatus.SUCCESSFUL, - messages=[_("Successfully imported %(count)d files." % {"count": count})], - ) + TaskStatus.SUCCESSFUL, _("Successfully imported %(count)d files." % {"count": count}) ) @@ -176,12 +192,12 @@ def check_blueprint_v1_file(blueprint: BlueprintFile): @CELERY_APP.task( bind=True, - base=MonitoredTask, + base=SystemTask, ) -def apply_blueprint(self: MonitoredTask, instance_pk: str): +def apply_blueprint(self: SystemTask, instance_pk: str): """Apply single blueprint""" self.save_on_success = False - instance: Optional[BlueprintInstance] = None + instance: BlueprintInstance | None = None try: instance: BlueprintInstance = BlueprintInstance.objects.filter(pk=instance_pk).first() if not instance or not instance.enabled: @@ -196,29 +212,30 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str): if not valid: instance.status = BlueprintInstanceStatus.ERROR instance.save() - self.set_status(TaskResult(TaskResultStatus.ERROR, [x["event"] for x in logs])) - return - applied = importer.apply() - if not applied: - instance.status = BlueprintInstanceStatus.ERROR - instance.save() - self.set_status(TaskResult(TaskResultStatus.ERROR, "Failed to apply")) + self.set_status(TaskStatus.ERROR, *logs) return + with capture_logs() as logs: + applied = importer.apply() + if not applied: + instance.status = BlueprintInstanceStatus.ERROR + instance.save() + self.set_status(TaskStatus.ERROR, *logs) + return instance.status = BlueprintInstanceStatus.SUCCESSFUL instance.last_applied_hash = file_hash instance.last_applied = now() - self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL)) + self.set_status(TaskStatus.SUCCESSFUL) except ( + OSError, DatabaseError, ProgrammingError, InternalError, - IOError, BlueprintRetrievalFailed, EntryInvalidError, ) as exc: if instance: instance.status = BlueprintInstanceStatus.ERROR - self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) + self.set_error(exc) finally: if instance: instance.save() diff --git a/website/help/.gitkeep b/authentik/brands/__init__.py similarity index 100% rename from website/help/.gitkeep rename to authentik/brands/__init__.py diff --git a/authentik/tenants/api.py b/authentik/brands/api.py similarity index 68% rename from authentik/tenants/api.py rename to authentik/brands/api.py index f777639701..3bf199b93b 100644 --- a/authentik/tenants/api.py +++ b/authentik/brands/api.py @@ -1,4 +1,5 @@ -"""Serializer for tenant models""" +"""Serializer for brands models""" + from typing import Any from django.db import models @@ -10,14 +11,14 @@ from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.permissions import AllowAny from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer +from rest_framework.validators import UniqueValidator from rest_framework.viewsets import ModelViewSet from authentik.api.authorization import SecretKeyFilter +from authentik.brands.models import Brand from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import PassiveSerializer -from authentik.lib.config import CONFIG -from authentik.tenants.models import Tenant +from authentik.core.api.utils import ModelSerializer, PassiveSerializer +from authentik.tenants.utils import get_current_tenant class FooterLinkSerializer(PassiveSerializer): @@ -27,22 +28,22 @@ class FooterLinkSerializer(PassiveSerializer): name = CharField(read_only=True) -class TenantSerializer(ModelSerializer): - """Tenant Serializer""" +class BrandSerializer(ModelSerializer): + """Brand Serializer""" def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: if attrs.get("default", False): - tenants = Tenant.objects.filter(default=True) + brands = Brand.objects.filter(default=True) if self.instance: - tenants = tenants.exclude(pk=self.instance.pk) - if tenants.exists(): - raise ValidationError({"default": "Only a single Tenant can be set as default."}) + brands = brands.exclude(pk=self.instance.pk) + if brands.exists(): + raise ValidationError({"default": "Only a single brand can be set as default."}) return super().validate(attrs) class Meta: - model = Tenant + model = Brand fields = [ - "tenant_uuid", + "brand_uuid", "domain", "default", "branding_title", @@ -54,10 +55,14 @@ class TenantSerializer(ModelSerializer): "flow_unenrollment", "flow_user_settings", "flow_device_code", - "event_retention", "web_certificate", "attributes", ] + extra_kwargs = { + # TODO: This field isn't unique on the database which is hard to backport + # hence we just validate the uniqueness here + "domain": {"validators": [UniqueValidator(Brand.objects.all())]}, + } class Themes(models.TextChoices): @@ -68,8 +73,13 @@ class Themes(models.TextChoices): DARK = "dark" -class CurrentTenantSerializer(PassiveSerializer): - """Partial tenant information for styling""" +def get_default_ui_footer_links(): + """Get default UI footer links based on current tenant settings""" + return get_current_tenant().footer_links + + +class CurrentBrandSerializer(PassiveSerializer): + """Partial brand information for styling""" matched_domain = CharField(source="domain") branding_title = CharField() @@ -78,7 +88,7 @@ class CurrentTenantSerializer(PassiveSerializer): ui_footer_links = ListField( child=FooterLinkSerializer(), read_only=True, - default=CONFIG.get("footer_links", []), + default=get_default_ui_footer_links, ) ui_theme = ChoiceField( choices=Themes.choices, @@ -97,18 +107,18 @@ class CurrentTenantSerializer(PassiveSerializer): default_locale = CharField(read_only=True) -class TenantViewSet(UsedByMixin, ModelViewSet): - """Tenant Viewset""" +class BrandViewSet(UsedByMixin, ModelViewSet): + """Brand Viewset""" - queryset = Tenant.objects.all() - serializer_class = TenantSerializer + queryset = Brand.objects.all() + serializer_class = BrandSerializer search_fields = [ "domain", "branding_title", "web_certificate__name", ] filterset_fields = [ - "tenant_uuid", + "brand_uuid", "domain", "default", "branding_title", @@ -120,7 +130,6 @@ class TenantViewSet(UsedByMixin, ModelViewSet): "flow_unenrollment", "flow_user_settings", "flow_device_code", - "event_retention", "web_certificate", ] ordering = ["domain"] @@ -128,10 +137,10 @@ class TenantViewSet(UsedByMixin, ModelViewSet): filter_backends = [SecretKeyFilter, OrderingFilter, SearchFilter] @extend_schema( - responses=CurrentTenantSerializer(many=False), + responses=CurrentBrandSerializer(many=False), ) @action(methods=["GET"], detail=False, permission_classes=[AllowAny]) def current(self, request: Request) -> Response: - """Get current tenant""" - tenant: Tenant = request._request.tenant - return Response(CurrentTenantSerializer(tenant).data) + """Get current brand""" + brand: Brand = request._request.brand + return Response(CurrentBrandSerializer(brand).data) diff --git a/authentik/brands/apps.py b/authentik/brands/apps.py new file mode 100644 index 0000000000..43c20acc75 --- /dev/null +++ b/authentik/brands/apps.py @@ -0,0 +1,11 @@ +"""authentik brands app""" + +from django.apps import AppConfig + + +class AuthentikBrandsConfig(AppConfig): + """authentik Brand app""" + + name = "authentik.brands" + label = "authentik_brands" + verbose_name = "authentik Brands" diff --git a/authentik/brands/middleware.py b/authentik/brands/middleware.py new file mode 100644 index 0000000000..71650cc621 --- /dev/null +++ b/authentik/brands/middleware.py @@ -0,0 +1,27 @@ +"""Inject brand into current request""" + +from collections.abc import Callable + +from django.http.request import HttpRequest +from django.http.response import HttpResponse +from django.utils.translation import activate + +from authentik.brands.utils import get_brand_for_request + + +class BrandMiddleware: + """Add current brand to http request""" + + get_response: Callable[[HttpRequest], HttpResponse] + + def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): + self.get_response = get_response + + def __call__(self, request: HttpRequest) -> HttpResponse: + if not hasattr(request, "brand"): + brand = get_brand_for_request(request) + request.brand = brand + locale = brand.default_locale + if locale != "": + activate(locale) + return self.get_response(request) diff --git a/authentik/tenants/migrations/0001_squashed_0005_tenant_web_certificate.py b/authentik/brands/migrations/0001_squashed_0005_tenant_web_certificate.py similarity index 80% rename from authentik/tenants/migrations/0001_squashed_0005_tenant_web_certificate.py rename to authentik/brands/migrations/0001_squashed_0005_tenant_web_certificate.py index 65cfaf01e7..4d0e592f55 100644 --- a/authentik/tenants/migrations/0001_squashed_0005_tenant_web_certificate.py +++ b/authentik/brands/migrations/0001_squashed_0005_tenant_web_certificate.py @@ -10,11 +10,11 @@ import authentik.lib.utils.time class Migration(migrations.Migration): replaces = [ - ("authentik_tenants", "0001_initial"), - ("authentik_tenants", "0002_default"), - ("authentik_tenants", "0003_tenant_branding_favicon"), - ("authentik_tenants", "0004_tenant_event_retention"), - ("authentik_tenants", "0005_tenant_web_certificate"), + ("authentik_brands", "0001_initial"), + ("authentik_brands", "0002_default"), + ("authentik_brands", "0003_tenant_branding_favicon"), + ("authentik_brands", "0004_tenant_event_retention"), + ("authentik_brands", "0005_tenant_web_certificate"), ] initial = True @@ -25,7 +25,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name="Tenant", + name="Brand", fields=[ ( "tenant_uuid", @@ -37,7 +37,7 @@ class Migration(migrations.Migration): "domain", models.TextField( help_text=( - "Domain that activates this tenant. Can be a superset, i.e. `a.b` for" + "Domain that activates this brand. Can be a superset, i.e. `a.b` for" " `aa.b` and `ba.b`" ) ), @@ -53,7 +53,7 @@ class Migration(migrations.Migration): models.ForeignKey( null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name="tenant_authentication", + related_name="brand_authentication", to="authentik_flows.flow", ), ), @@ -62,7 +62,7 @@ class Migration(migrations.Migration): models.ForeignKey( null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name="tenant_invalidation", + related_name="brand_invalidation", to="authentik_flows.flow", ), ), @@ -71,7 +71,7 @@ class Migration(migrations.Migration): models.ForeignKey( null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name="tenant_recovery", + related_name="brand_recovery", to="authentik_flows.flow", ), ), @@ -80,23 +80,23 @@ class Migration(migrations.Migration): models.ForeignKey( null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name="tenant_unenrollment", + related_name="brand_unenrollment", to="authentik_flows.flow", ), ), ], options={ - "verbose_name": "Tenant", - "verbose_name_plural": "Tenants", + "verbose_name": "Brand", + "verbose_name_plural": "Brands", }, ), migrations.AddField( - model_name="tenant", + model_name="brand", name="branding_favicon", field=models.TextField(default="/static/dist/assets/icons/icon.png"), ), migrations.AddField( - model_name="tenant", + model_name="brand", name="event_retention", field=models.TextField( default="days=365", @@ -108,7 +108,7 @@ class Migration(migrations.Migration): ), ), migrations.AddField( - model_name="tenant", + model_name="brand", name="web_certificate", field=models.ForeignKey( default=None, diff --git a/authentik/tenants/migrations/0002_tenant_flow_user_settings.py b/authentik/brands/migrations/0002_tenant_flow_user_settings.py similarity index 79% rename from authentik/tenants/migrations/0002_tenant_flow_user_settings.py rename to authentik/brands/migrations/0002_tenant_flow_user_settings.py index 2f17db4a13..6c356f2d30 100644 --- a/authentik/tenants/migrations/0002_tenant_flow_user_settings.py +++ b/authentik/brands/migrations/0002_tenant_flow_user_settings.py @@ -8,17 +8,17 @@ class Migration(migrations.Migration): dependencies = [ ("authentik_stages_prompt", "0007_prompt_placeholder_expression"), ("authentik_flows", "0021_auto_20211227_2103"), - ("authentik_tenants", "0001_squashed_0005_tenant_web_certificate"), + ("authentik_brands", "0001_squashed_0005_tenant_web_certificate"), ] operations = [ migrations.AddField( - model_name="tenant", + model_name="brand", name="flow_user_settings", field=models.ForeignKey( null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name="tenant_user_settings", + related_name="brand_user_settings", to="authentik_flows.flow", ), ), diff --git a/authentik/tenants/migrations/0003_tenant_attributes.py b/authentik/brands/migrations/0003_tenant_attributes.py similarity index 76% rename from authentik/tenants/migrations/0003_tenant_attributes.py rename to authentik/brands/migrations/0003_tenant_attributes.py index 3431af7f09..221ca9c1c5 100644 --- a/authentik/tenants/migrations/0003_tenant_attributes.py +++ b/authentik/brands/migrations/0003_tenant_attributes.py @@ -5,12 +5,12 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ("authentik_tenants", "0002_tenant_flow_user_settings"), + ("authentik_brands", "0002_tenant_flow_user_settings"), ] operations = [ migrations.AddField( - model_name="tenant", + model_name="brand", name="attributes", field=models.JSONField(blank=True, default=dict), ), diff --git a/authentik/tenants/migrations/0004_tenant_flow_device_code.py b/authentik/brands/migrations/0004_tenant_flow_device_code.py similarity index 79% rename from authentik/tenants/migrations/0004_tenant_flow_device_code.py rename to authentik/brands/migrations/0004_tenant_flow_device_code.py index 2e2fdcf458..50de430d52 100644 --- a/authentik/tenants/migrations/0004_tenant_flow_device_code.py +++ b/authentik/brands/migrations/0004_tenant_flow_device_code.py @@ -7,17 +7,17 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("authentik_flows", "0023_flow_denied_action"), - ("authentik_tenants", "0003_tenant_attributes"), + ("authentik_brands", "0003_tenant_attributes"), ] operations = [ migrations.AddField( - model_name="tenant", + model_name="brand", name="flow_device_code", field=models.ForeignKey( null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name="tenant_device_code", + related_name="brand_device_code", to="authentik_flows.flow", ), ), diff --git a/authentik/brands/migrations/0005_tenantuuid_to_branduuid.py b/authentik/brands/migrations/0005_tenantuuid_to_branduuid.py new file mode 100644 index 0000000000..89f777822e --- /dev/null +++ b/authentik/brands/migrations/0005_tenantuuid_to_branduuid.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2023-12-12 06:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_brands", "0004_tenant_flow_device_code"), + ] + + operations = [ + migrations.RenameField( + model_name="brand", + old_name="tenant_uuid", + new_name="brand_uuid", + ), + migrations.RemoveField( + model_name="brand", + name="event_retention", + ), + ] diff --git a/authentik/brands/migrations/0006_brand_authentik_b_domain_b9b24a_idx_and_more.py b/authentik/brands/migrations/0006_brand_authentik_b_domain_b9b24a_idx_and_more.py new file mode 100644 index 0000000000..edcbd34f8d --- /dev/null +++ b/authentik/brands/migrations/0006_brand_authentik_b_domain_b9b24a_idx_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.4 on 2024-04-18 18:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_brands", "0005_tenantuuid_to_branduuid"), + ] + + operations = [ + migrations.AddIndex( + model_name="brand", + index=models.Index(fields=["domain"], name="authentik_b_domain_b9b24a_idx"), + ), + migrations.AddIndex( + model_name="brand", + index=models.Index(fields=["default"], name="authentik_b_default_3ccf12_idx"), + ), + ] diff --git a/authentik/brands/migrations/__init__.py b/authentik/brands/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/authentik/brands/models.py b/authentik/brands/models.py new file mode 100644 index 0000000000..0193774935 --- /dev/null +++ b/authentik/brands/models.py @@ -0,0 +1,90 @@ +"""brand models""" + +from uuid import uuid4 + +from django.db import models +from django.utils.translation import gettext_lazy as _ +from rest_framework.serializers import Serializer +from structlog.stdlib import get_logger + +from authentik.crypto.models import CertificateKeyPair +from authentik.flows.models import Flow +from authentik.lib.models import SerializerModel + +LOGGER = get_logger() + + +class Brand(SerializerModel): + """Single brand""" + + brand_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) + domain = models.TextField( + help_text=_( + "Domain that activates this brand. Can be a superset, i.e. `a.b` for `aa.b` and `ba.b`" + ) + ) + default = models.BooleanField( + default=False, + ) + + branding_title = models.TextField(default="authentik") + + branding_logo = models.TextField(default="/static/dist/assets/icons/icon_left_brand.svg") + branding_favicon = models.TextField(default="/static/dist/assets/icons/icon.png") + + flow_authentication = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="brand_authentication" + ) + flow_invalidation = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="brand_invalidation" + ) + flow_recovery = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="brand_recovery" + ) + flow_unenrollment = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="brand_unenrollment" + ) + flow_user_settings = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="brand_user_settings" + ) + flow_device_code = models.ForeignKey( + Flow, null=True, on_delete=models.SET_NULL, related_name="brand_device_code" + ) + + web_certificate = models.ForeignKey( + CertificateKeyPair, + null=True, + default=None, + on_delete=models.SET_DEFAULT, + help_text=_("Web Certificate used by the authentik Core webserver."), + ) + attributes = models.JSONField(default=dict, blank=True) + + @property + def serializer(self) -> Serializer: + from authentik.brands.api import BrandSerializer + + return BrandSerializer + + @property + def default_locale(self) -> str: + """Get default locale""" + try: + return self.attributes.get("settings", {}).get("locale", "") + + except Exception as exc: + LOGGER.warning("Failed to get default locale", exc=exc) + return "" + + def __str__(self) -> str: + if self.default: + return "Default brand" + return f"Brand {self.domain}" + + class Meta: + verbose_name = _("Brand") + verbose_name_plural = _("Brands") + indexes = [ + models.Index(fields=["domain"]), + models.Index(fields=["default"]), + ] diff --git a/authentik/brands/tests.py b/authentik/brands/tests.py new file mode 100644 index 0000000000..712818fda1 --- /dev/null +++ b/authentik/brands/tests.py @@ -0,0 +1,77 @@ +"""Test brands""" + +from django.urls import reverse +from rest_framework.test import APITestCase + +from authentik.brands.api import Themes +from authentik.brands.models import Brand +from authentik.core.tests.utils import create_test_admin_user, create_test_brand + + +class TestBrands(APITestCase): + """Test brands""" + + def test_current_brand(self): + """Test Current brand API""" + brand = create_test_brand() + self.assertJSONEqual( + self.client.get(reverse("authentik_api:brand-current")).content.decode(), + { + "branding_logo": "/static/dist/assets/icons/icon_left_brand.svg", + "branding_favicon": "/static/dist/assets/icons/icon.png", + "branding_title": "authentik", + "matched_domain": brand.domain, + "ui_footer_links": [], + "ui_theme": Themes.AUTOMATIC, + "default_locale": "", + }, + ) + + def test_brand_subdomain(self): + """Test Current brand API""" + Brand.objects.all().delete() + Brand.objects.create(domain="bar.baz", branding_title="custom") + self.assertJSONEqual( + self.client.get( + reverse("authentik_api:brand-current"), HTTP_HOST="foo.bar.baz" + ).content.decode(), + { + "branding_logo": "/static/dist/assets/icons/icon_left_brand.svg", + "branding_favicon": "/static/dist/assets/icons/icon.png", + "branding_title": "custom", + "matched_domain": "bar.baz", + "ui_footer_links": [], + "ui_theme": Themes.AUTOMATIC, + "default_locale": "", + }, + ) + + def test_fallback(self): + """Test fallback brand""" + Brand.objects.all().delete() + self.assertJSONEqual( + self.client.get(reverse("authentik_api:brand-current")).content.decode(), + { + "branding_logo": "/static/dist/assets/icons/icon_left_brand.svg", + "branding_favicon": "/static/dist/assets/icons/icon.png", + "branding_title": "authentik", + "matched_domain": "fallback", + "ui_footer_links": [], + "ui_theme": Themes.AUTOMATIC, + "default_locale": "", + }, + ) + + def test_create_default_multiple(self): + """Test attempted creation of multiple default brands""" + Brand.objects.create( + domain="foo", + default=True, + branding_title="custom", + ) + user = create_test_admin_user() + self.client.force_login(user) + response = self.client.post( + reverse("authentik_api:brand-list"), data={"domain": "bar", "default": True} + ) + self.assertEqual(response.status_code, 400) diff --git a/authentik/brands/urls.py b/authentik/brands/urls.py new file mode 100644 index 0000000000..85b0ea2432 --- /dev/null +++ b/authentik/brands/urls.py @@ -0,0 +1,7 @@ +"""API URLs""" + +from authentik.brands.api import BrandViewSet + +api_urlpatterns = [ + ("core/brands", BrandViewSet), +] diff --git a/authentik/brands/utils.py b/authentik/brands/utils.py new file mode 100644 index 0000000000..5e6ef40ed3 --- /dev/null +++ b/authentik/brands/utils.py @@ -0,0 +1,44 @@ +"""Brand utilities""" + +from typing import Any + +from django.db.models import F, Q +from django.db.models import Value as V +from django.http.request import HttpRequest +from sentry_sdk.hub import Hub + +from authentik import get_full_version +from authentik.brands.models import Brand +from authentik.tenants.models import Tenant + +_q_default = Q(default=True) +DEFAULT_BRAND = Brand(domain="fallback") + + +def get_brand_for_request(request: HttpRequest) -> Brand: + """Get brand object for current request""" + db_brands = ( + Brand.objects.annotate(host_domain=V(request.get_host())) + .filter(Q(host_domain__iendswith=F("domain")) | _q_default) + .order_by("default") + ) + brands = list(db_brands.all()) + if len(brands) < 1: + return DEFAULT_BRAND + return brands[0] + + +def context_processor(request: HttpRequest) -> dict[str, Any]: + """Context Processor that injects brand object into every template""" + brand = getattr(request, "brand", DEFAULT_BRAND) + tenant = getattr(request, "tenant", Tenant()) + trace = "" + span = Hub.current.scope.span + if span: + trace = span.to_traceparent() + return { + "brand": brand, + "footer_links": tenant.footer_links, + "sentry_trace": trace, + "version": get_full_version(), + } diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index cb4c01e768..cc747fd8c8 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -1,33 +1,34 @@ """Application API Views""" + +from collections.abc import Iterator +from copy import copy from datetime import timedelta -from typing import Optional from django.core.cache import cache from django.db.models import QuerySet from django.db.models.functions import ExtractHour -from django.http.response import HttpResponseBadRequest from django.shortcuts import get_object_or_404 from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action +from rest_framework.exceptions import ValidationError from rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet from structlog.stdlib import get_logger -from structlog.testing import capture_logs from authentik.admin.api.metrics import CoordinateSerializer -from authentik.api.decorators import permission_required +from authentik.api.pagination import Pagination from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.core.api.providers import ProviderSerializer from authentik.core.api.used_by import UsedByMixin +from authentik.core.api.utils import ModelSerializer from authentik.core.models import Application, User +from authentik.events.logs import LogEventSerializer, capture_logs from authentik.events.models import EventAction -from authentik.events.utils import sanitize_dict from authentik.lib.utils.file import ( FilePathSerializer, FileUploadSerializer, @@ -36,15 +37,19 @@ from authentik.lib.utils.file import ( ) from authentik.policies.api.exec import PolicyTestResultSerializer from authentik.policies.engine import PolicyEngine -from authentik.policies.types import PolicyResult +from authentik.policies.types import CACHE_PREFIX, PolicyResult +from authentik.rbac.decorators import permission_required from authentik.rbac.filters import ObjectFilter LOGGER = get_logger() -def user_app_cache_key(user_pk: str) -> str: +def user_app_cache_key(user_pk: str, page_number: int | None = None) -> str: """Cache key where application list for user is saved""" - return f"goauthentik.io/core/app_access/{user_pk}" + key = f"{CACHE_PREFIX}/app_access/{user_pk}" + if page_number: + key += f"/{page_number}" + return key class ApplicationSerializer(ModelSerializer): @@ -58,7 +63,7 @@ class ApplicationSerializer(ModelSerializer): meta_icon = ReadOnlyField(source="get_meta_icon") - def get_launch_url(self, app: Application) -> Optional[str]: + def get_launch_url(self, app: Application) -> str | None: """Allow formatting of launch URL""" user = None if "request" in self.context: @@ -98,7 +103,6 @@ class ApplicationSerializer(ModelSerializer): class ApplicationViewSet(UsedByMixin, ModelViewSet): """Application Viewset""" - # pylint: disable=no-member queryset = Application.objects.all().prefetch_related("provider") serializer_class = ApplicationSerializer search_fields = [ @@ -128,10 +132,16 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): queryset = backend().filter_queryset(self.request, queryset, self) return queryset - def _get_allowed_applications(self, queryset: QuerySet) -> list[Application]: + def _get_allowed_applications( + self, pagined_apps: Iterator[Application], user: User | None = None + ) -> list[Application]: applications = [] - for application in queryset: - engine = PolicyEngine(application, self.request.user, self.request) + request = self.request._request + if user: + request = copy(request) + request.user = user + for application in pagined_apps: + engine = PolicyEngine(application, request.user, request) engine.build() if engine.passing: applications.append(application) @@ -147,7 +157,6 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): ], responses={ 200: PolicyTestResultSerializer(), - 404: OpenApiResponse(description="for_user user not found"), }, ) @action(detail=True, methods=["GET"]) @@ -160,9 +169,11 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): for_user = request.user if request.user.is_superuser and "for_user" in request.query_params: try: - for_user = get_object_or_404(User, pk=request.query_params.get("for_user")) + for_user = User.objects.filter(pk=request.query_params.get("for_user")).first() except ValueError: - return HttpResponseBadRequest("for_user must be numerical") + raise ValidationError({"for_user": "for_user must be numerical"}) from None + if not for_user: + raise ValidationError({"for_user": "User not found"}) engine = PolicyEngine(application, for_user, request) engine.use_cache = False with capture_logs() as logs: @@ -174,9 +185,9 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): if request.user.is_superuser: log_messages = [] for log in logs: - if log.get("process", "") == "PolicyProcess": + if log.attributes.get("process", "") == "PolicyProcess": continue - log_messages.append(sanitize_dict(log)) + log_messages.append(LogEventSerializer(log).data) result.log_messages = log_messages response = PolicyTestResultSerializer(result) return Response(response.data) @@ -187,30 +198,56 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): name="superuser_full_list", location=OpenApiParameter.QUERY, type=OpenApiTypes.BOOL, - ) + ), + OpenApiParameter( + name="for_user", + location=OpenApiParameter.QUERY, + type=OpenApiTypes.INT, + ), ] ) def list(self, request: Request) -> Response: """Custom list method that checks Policy based access instead of guardian""" - should_cache = request.GET.get("search", "") == "" + should_cache = request.query_params.get("search", "") == "" - superuser_full_list = str(request.GET.get("superuser_full_list", "false")).lower() == "true" + superuser_full_list = ( + str(request.query_params.get("superuser_full_list", "false")).lower() == "true" + ) if superuser_full_list and request.user.is_superuser: return super().list(request) queryset = self._filter_queryset_for_list(self.get_queryset()) - self.paginate_queryset(queryset) + paginator: Pagination = self.paginator + paginated_apps = paginator.paginate_queryset(queryset, request) + + if "for_user" in request.query_params: + try: + for_user: int = int(request.query_params.get("for_user", 0)) + for_user = ( + get_objects_for_user(request.user, "authentik_core.view_user_applications") + .filter(pk=for_user) + .first() + ) + if not for_user: + raise ValidationError({"for_user": "User not found"}) + except ValueError as exc: + raise ValidationError from exc + allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user) + serializer = self.get_serializer(allowed_applications, many=True) + return self.get_paginated_response(serializer.data) allowed_applications = [] if not should_cache: - allowed_applications = self._get_allowed_applications(queryset) + allowed_applications = self._get_allowed_applications(paginated_apps) if should_cache: - allowed_applications = cache.get(user_app_cache_key(self.request.user.pk)) + allowed_applications = cache.get( + user_app_cache_key(self.request.user.pk, paginator.page.number) + ) if not allowed_applications: - LOGGER.debug("Caching allowed application list") - allowed_applications = self._get_allowed_applications(queryset) + LOGGER.debug("Caching allowed application list", page=paginator.page.number) + allowed_applications = self._get_allowed_applications(paginated_apps) cache.set( - user_app_cache_key(self.request.user.pk), + user_app_cache_key(self.request.user.pk, paginator.page.number), allowed_applications, timeout=86400, ) diff --git a/authentik/core/api/authenticated_sessions.py b/authentik/core/api/authenticated_sessions.py index 03c1aeaf3c..3e8c0b34ab 100644 --- a/authentik/core/api/authenticated_sessions.py +++ b/authentik/core/api/authenticated_sessions.py @@ -1,5 +1,6 @@ """AuthenticatedSessions API Viewset""" -from typing import Optional, TypedDict + +from typing import TypedDict from django_filters.rest_framework import DjangoFilterBackend from guardian.utils import get_anonymous_user @@ -7,14 +8,15 @@ from rest_framework import mixins from rest_framework.fields import SerializerMethodField from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.request import Request -from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import GenericViewSet from ua_parser import user_agent_parser from authentik.api.authorization import OwnerSuperuserPermissions from authentik.core.api.used_by import UsedByMixin +from authentik.core.api.utils import ModelSerializer from authentik.core.models import AuthenticatedSession -from authentik.events.geo import GEOIP_READER, GeoIPDict +from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR, ASNDict +from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR, GeoIPDict class UserAgentDeviceDict(TypedDict): @@ -59,6 +61,7 @@ class AuthenticatedSessionSerializer(ModelSerializer): current = SerializerMethodField() user_agent = SerializerMethodField() geo_ip = SerializerMethodField() + asn = SerializerMethodField() def get_current(self, instance: AuthenticatedSession) -> bool: """Check if session is currently active session""" @@ -69,9 +72,13 @@ class AuthenticatedSessionSerializer(ModelSerializer): """Get parsed user agent""" return user_agent_parser.Parse(instance.last_user_agent) - def get_geo_ip(self, instance: AuthenticatedSession) -> Optional[GeoIPDict]: # pragma: no cover - """Get parsed user agent""" - return GEOIP_READER.city_dict(instance.last_ip) + def get_geo_ip(self, instance: AuthenticatedSession) -> GeoIPDict | None: # pragma: no cover + """Get GeoIP Data""" + return GEOIP_CONTEXT_PROCESSOR.city_dict(instance.last_ip) + + def get_asn(self, instance: AuthenticatedSession) -> ASNDict | None: # pragma: no cover + """Get ASN Data""" + return ASN_CONTEXT_PROCESSOR.asn_dict(instance.last_ip) class Meta: model = AuthenticatedSession @@ -80,6 +87,7 @@ class AuthenticatedSessionSerializer(ModelSerializer): "current", "user_agent", "geo_ip", + "asn", "user", "last_ip", "last_user_agent", diff --git a/authentik/core/api/devices.py b/authentik/core/api/devices.py index 7773615332..528d1775f7 100644 --- a/authentik/core/api/devices.py +++ b/authentik/core/api/devices.py @@ -1,4 +1,5 @@ """Authenticator Devices API Views""" + from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema from rest_framework.fields import BooleanField, CharField, IntegerField, SerializerMethodField diff --git a/authentik/core/api/groups.py b/authentik/core/api/groups.py index 21ba19974d..fd9358152b 100644 --- a/authentik/core/api/groups.py +++ b/authentik/core/api/groups.py @@ -1,30 +1,37 @@ """Groups API Viewset""" -from json import loads -from typing import Optional +from json import loads + +from django.db.models import Prefetch from django.http import Http404 from django_filters.filters import CharFilter, ModelMultipleChoiceFilter from django_filters.filterset import FilterSet -from drf_spectacular.utils import OpenApiResponse, extend_schema +from drf_spectacular.utils import ( + OpenApiParameter, + OpenApiResponse, + extend_schema, + extend_schema_field, +) from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action -from rest_framework.fields import CharField, IntegerField, JSONField +from rest_framework.fields import CharField, IntegerField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError +from rest_framework.serializers import ListSerializer, ValidationError +from rest_framework.validators import UniqueValidator from rest_framework.viewsets import ModelViewSet -from authentik.api.decorators import permission_required from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import PassiveSerializer, is_dict +from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer from authentik.core.models import Group, User from authentik.rbac.api.roles import RoleSerializer +from authentik.rbac.decorators import permission_required class GroupMemberSerializer(ModelSerializer): """Stripped down user serializer to show relevant users for groups""" - attributes = JSONField(validators=[is_dict], required=False) + attributes = JSONDictField(required=False) uid = CharField(read_only=True) class Meta: @@ -44,10 +51,8 @@ class GroupMemberSerializer(ModelSerializer): class GroupSerializer(ModelSerializer): """Group Serializer""" - attributes = JSONField(validators=[is_dict], required=False) - users_obj = ListSerializer( - child=GroupMemberSerializer(), read_only=True, source="users", required=False - ) + attributes = JSONDictField(required=False) + users_obj = SerializerMethodField(allow_null=True) roles_obj = ListSerializer( child=RoleSerializer(), read_only=True, @@ -58,7 +63,20 @@ class GroupSerializer(ModelSerializer): num_pk = IntegerField(read_only=True) - def validate_parent(self, parent: Optional[Group]): + @property + def _should_include_users(self) -> bool: + request: Request = self.context.get("request", None) + if not request: + return True + return str(request.query_params.get("include_users", "true")).lower() == "true" + + @extend_schema_field(GroupMemberSerializer(many=True)) + def get_users_obj(self, instance: Group) -> list[GroupMemberSerializer] | None: + if not self._should_include_users: + return None + return GroupMemberSerializer(instance.users, many=True).data + + def validate_parent(self, parent: Group | None): """Validate group parent (if set), ensuring the parent isn't itself""" if not self.instance or not parent: return parent @@ -84,7 +102,10 @@ class GroupSerializer(ModelSerializer): extra_kwargs = { "users": { "default": list, - } + }, + # TODO: This field isn't unique on the database which is hard to backport + # hence we just validate the uniqueness here + "name": {"validators": [UniqueValidator(Group.objects.all())]}, } @@ -113,7 +134,7 @@ class GroupFilter(FilterSet): try: value = loads(value) except ValueError: - raise ValidationError(detail="filter: failed to parse JSON") + raise ValidationError(detail="filter: failed to parse JSON") from None if not isinstance(value, dict): raise ValidationError(detail="filter: value must be key:value mapping") qs = {} @@ -130,23 +151,49 @@ class GroupFilter(FilterSet): fields = ["name", "is_superuser", "members_by_pk", "attributes", "members_by_username"] -class UserAccountSerializer(PassiveSerializer): - """Account adding/removing operations""" - - pk = IntegerField(required=True) - - class GroupViewSet(UsedByMixin, ModelViewSet): """Group Viewset""" - # pylint: disable=no-member - queryset = Group.objects.all().select_related("parent").prefetch_related("users") + class UserAccountSerializer(PassiveSerializer): + """Account adding/removing operations""" + + pk = IntegerField(required=True) + + queryset = Group.objects.none() serializer_class = GroupSerializer search_fields = ["name", "is_superuser"] filterset_class = GroupFilter ordering = ["name"] - @permission_required(None, ["authentik_core.add_user"]) + def get_queryset(self): + base_qs = Group.objects.all().select_related("parent").prefetch_related("roles") + + if self.serializer_class(context={"request": self.request})._should_include_users: + base_qs = base_qs.prefetch_related("users") + else: + base_qs = base_qs.prefetch_related( + Prefetch("users", queryset=User.objects.all().only("id")) + ) + + return base_qs + + @extend_schema( + parameters=[ + OpenApiParameter("include_users", bool, default=True), + ] + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + parameters=[ + OpenApiParameter("include_users", bool, default=True), + ] + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + + @permission_required("authentik_core.add_user_to_group") @extend_schema( request=UserAccountSerializer, responses={ @@ -154,7 +201,13 @@ class GroupViewSet(UsedByMixin, ModelViewSet): 404: OpenApiResponse(description="User not found"), }, ) - @action(detail=True, methods=["POST"], pagination_class=None, filter_backends=[]) + @action( + detail=True, + methods=["POST"], + pagination_class=None, + filter_backends=[], + permission_classes=[], + ) def add_user(self, request: Request, pk: str) -> Response: """Add user to group""" group: Group = self.get_object() @@ -170,7 +223,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet): group.users.add(user) return Response(status=204) - @permission_required(None, ["authentik_core.add_user"]) + @permission_required("authentik_core.remove_user_from_group") @extend_schema( request=UserAccountSerializer, responses={ @@ -178,7 +231,13 @@ class GroupViewSet(UsedByMixin, ModelViewSet): 404: OpenApiResponse(description="User not found"), }, ) - @action(detail=True, methods=["POST"], pagination_class=None, filter_backends=[]) + @action( + detail=True, + methods=["POST"], + pagination_class=None, + filter_backends=[], + permission_classes=[], + ) def remove_user(self, request: Request, pk: str) -> Response: """Add user to group""" group: Group = self.get_object() diff --git a/authentik/core/api/object_types.py b/authentik/core/api/object_types.py new file mode 100644 index 0000000000..12acd4e4a7 --- /dev/null +++ b/authentik/core/api/object_types.py @@ -0,0 +1,79 @@ +"""API Utilities""" + +from drf_spectacular.utils import extend_schema +from rest_framework.decorators import action +from rest_framework.fields import ( + BooleanField, + CharField, +) +from rest_framework.request import Request +from rest_framework.response import Response + +from authentik.core.api.utils import PassiveSerializer +from authentik.enterprise.apps import EnterpriseConfig +from authentik.lib.utils.reflection import all_subclasses + + +class TypeCreateSerializer(PassiveSerializer): + """Types of an object that can be created""" + + name = CharField(required=True) + description = CharField(required=True) + component = CharField(required=True) + model_name = CharField(required=True) + + icon_url = CharField(required=False) + requires_enterprise = BooleanField(default=False) + + +class CreatableType: + """Class to inherit from to mark a model as creatable, even if the model itself is marked + as abstract""" + + +class NonCreatableType: + """Class to inherit from to mark a model as non-creatable even if it is not abstract""" + + +class TypesMixin: + """Mixin which adds an API endpoint to list all possible types that can be created""" + + @extend_schema(responses={200: TypeCreateSerializer(many=True)}) + @action(detail=False, pagination_class=None, filter_backends=[]) + def types(self, request: Request, additional: list[dict] | None = None) -> Response: + """Get all creatable types""" + data = [] + for subclass in all_subclasses(self.queryset.model): + instance = None + if subclass._meta.abstract: + if not issubclass(subclass, CreatableType): + continue + # Circumvent the django protection for not being able to instantiate + # abstract models. We need a model instance to access .component + # and further down .icon_url + instance = subclass.__new__(subclass) + # Django re-sets abstract = False so we need to override that + instance.Meta.abstract = True + else: + if issubclass(subclass, NonCreatableType): + continue + instance = subclass() + try: + data.append( + { + "name": subclass._meta.verbose_name, + "description": subclass.__doc__, + "component": instance.component, + "model_name": subclass._meta.model_name, + "icon_url": getattr(instance, "icon_url", None), + "requires_enterprise": isinstance( + subclass._meta.app_config, EnterpriseConfig + ), + } + ) + except NotImplementedError: + continue + if additional: + data.extend(additional) + data = sorted(data, key=lambda x: x["name"]) + return Response(TypeCreateSerializer(data, many=True).data) diff --git a/authentik/core/api/propertymappings.py b/authentik/core/api/property_mappings.py similarity index 59% rename from authentik/core/api/propertymappings.py rename to authentik/core/api/property_mappings.py index 1e7436be93..34c8fa5910 100644 --- a/authentik/core/api/propertymappings.py +++ b/authentik/core/api/property_mappings.py @@ -1,4 +1,5 @@ """PropertyMapping API Views""" + from json import dumps from drf_spectacular.types import OpenApiTypes @@ -7,21 +8,25 @@ from guardian.shortcuts import get_objects_for_user from rest_framework import mixins from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied -from rest_framework.fields import BooleanField, CharField +from rest_framework.fields import BooleanField, CharField, SerializerMethodField +from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.viewsets import GenericViewSet -from authentik.api.decorators import permission_required from authentik.blueprints.api import ManagedSerializer +from authentik.core.api.object_types import TypesMixin from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer, TypeCreateSerializer +from authentik.core.api.utils import ( + MetaNameSerializer, + ModelSerializer, + PassiveSerializer, +) from authentik.core.expression.evaluator import PropertyMappingEvaluator -from authentik.core.models import PropertyMapping +from authentik.core.models import Group, PropertyMapping, User from authentik.events.utils import sanitize_item -from authentik.lib.utils.reflection import all_subclasses from authentik.policies.api.exec import PolicyTestSerializer +from authentik.rbac.decorators import permission_required class PropertyMappingTestResultSerializer(PassiveSerializer): @@ -63,6 +68,7 @@ class PropertyMappingSerializer(ManagedSerializer, ModelSerializer, MetaNameSeri class PropertyMappingViewSet( + TypesMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, UsedByMixin, @@ -71,7 +77,15 @@ class PropertyMappingViewSet( ): """PropertyMapping Viewset""" - queryset = PropertyMapping.objects.none() + class PropertyMappingTestSerializer(PolicyTestSerializer): + """Test property mapping execution for a user/group with context""" + + user = PrimaryKeyRelatedField(queryset=User.objects.all(), required=False, allow_null=True) + group = PrimaryKeyRelatedField( + queryset=Group.objects.all(), required=False, allow_null=True + ) + + queryset = PropertyMapping.objects.select_subclasses() serializer_class = PropertyMappingSerializer search_fields = [ "name", @@ -79,29 +93,9 @@ class PropertyMappingViewSet( filterset_fields = {"managed": ["isnull"]} ordering = ["name"] - def get_queryset(self): # pragma: no cover - return PropertyMapping.objects.select_subclasses() - - @extend_schema(responses={200: TypeCreateSerializer(many=True)}) - @action(detail=False, pagination_class=None, filter_backends=[]) - def types(self, request: Request) -> Response: - """Get all creatable property-mapping types""" - data = [] - for subclass in all_subclasses(self.queryset.model): - subclass: PropertyMapping - data.append( - { - "name": subclass._meta.verbose_name, - "description": subclass.__doc__, - "component": subclass().component, - "model_name": subclass._meta.model_name, - } - ) - return Response(TypeCreateSerializer(data, many=True).data) - @permission_required("authentik_core.view_propertymapping") @extend_schema( - request=PolicyTestSerializer(), + request=PropertyMappingTestSerializer(), responses={ 200: PropertyMappingTestResultSerializer, 400: OpenApiResponse(description="Invalid parameters"), @@ -117,31 +111,45 @@ class PropertyMappingViewSet( @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) def test(self, request: Request, pk: str) -> Response: """Test Property Mapping""" - mapping: PropertyMapping = self.get_object() - test_params = PolicyTestSerializer(data=request.data) + _mapping: PropertyMapping = self.get_object() + # Use `get_subclass` to get correct class and correct `.evaluate` implementation + mapping: PropertyMapping = PropertyMapping.objects.get_subclass(pk=_mapping.pk) + # FIXME: when we separate policy mappings between ones for sources + # and ones for providers, we need to make the user field optional for the source mapping + test_params = self.PropertyMappingTestSerializer(data=request.data) if not test_params.is_valid(): return Response(test_params.errors, status=400) format_result = str(request.GET.get("format_result", "false")).lower() == "true" - # User permission check, only allow mapping testing for users that are readable - users = get_objects_for_user(request.user, "authentik_core.view_user").filter( - pk=test_params.validated_data["user"].pk - ) - if not users.exists(): - raise PermissionDenied() + context: dict = test_params.validated_data.get("context", {}) + context.setdefault("user", None) + + if user := test_params.validated_data.get("user"): + # User permission check, only allow mapping testing for users that are readable + users = get_objects_for_user(request.user, "authentik_core.view_user").filter( + pk=user.pk + ) + if not users.exists(): + raise PermissionDenied() + context["user"] = user + if group := test_params.validated_data.get("group"): + # Group permission check, only allow mapping testing for groups that are readable + groups = get_objects_for_user(request.user, "authentik_core.view_group").filter( + pk=group.pk + ) + if not groups.exists(): + raise PermissionDenied() + context["group"] = group + context["request"] = self.request response_data = {"successful": True, "result": ""} try: - result = mapping.evaluate( - users.first(), - self.request, - **test_params.validated_data.get("context", {}), - ) + result = mapping.evaluate(**context) response_data["result"] = dumps( sanitize_item(result), indent=(4 if format_result else None) ) - except Exception as exc: # pylint: disable=broad-except + except Exception as exc: response_data["result"] = str(exc) response_data["successful"] = False response = PropertyMappingTestResultSerializer(response_data) diff --git a/authentik/core/api/providers.py b/authentik/core/api/providers.py index a5095dcde9..ae5547fe66 100644 --- a/authentik/core/api/providers.py +++ b/authentik/core/api/providers.py @@ -1,22 +1,18 @@ """Provider API Views""" + from django.db.models import QuerySet from django.db.models.query import Q from django.utils.translation import gettext_lazy as _ from django_filters.filters import BooleanFilter from django_filters.filterset import FilterSet -from drf_spectacular.utils import extend_schema from rest_framework import mixins -from rest_framework.decorators import action -from rest_framework.fields import ReadOnlyField -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer, SerializerMethodField +from rest_framework.fields import ReadOnlyField, SerializerMethodField from rest_framework.viewsets import GenericViewSet +from authentik.core.api.object_types import TypesMixin from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer +from authentik.core.api.utils import MetaNameSerializer, ModelSerializer from authentik.core.models import Provider -from authentik.lib.utils.reflection import all_subclasses class ProviderSerializer(ModelSerializer, MetaNameSerializer): @@ -61,8 +57,12 @@ class ProviderFilter(FilterSet): """Filter for providers""" application__isnull = BooleanFilter(method="filter_application__isnull") - backchannel_only = BooleanFilter( - method="filter_backchannel_only", + backchannel = BooleanFilter( + method="filter_backchannel", + label=_( + "When not set all providers are returned. When set to true, only backchannel " + "providers are returned. When set to false, backchannel providers are excluded" + ), ) def filter_application__isnull(self, queryset: QuerySet, name, value): @@ -73,12 +73,14 @@ class ProviderFilter(FilterSet): | Q(application__isnull=value) ) - def filter_backchannel_only(self, queryset: QuerySet, name, value): - """Only return backchannel providers""" + def filter_backchannel(self, queryset: QuerySet, name, value): + """By default all providers are returned. When set to true, only backchannel providers are + returned. When set to false, backchannel providers are excluded""" return queryset.filter(is_backchannel=value) class ProviderViewSet( + TypesMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, UsedByMixin, @@ -97,30 +99,3 @@ class ProviderViewSet( def get_queryset(self): # pragma: no cover return Provider.objects.select_subclasses() - - @extend_schema(responses={200: TypeCreateSerializer(many=True)}) - @action(detail=False, pagination_class=None, filter_backends=[]) - def types(self, request: Request) -> Response: - """Get all creatable provider types""" - data = [] - for subclass in all_subclasses(self.queryset.model): - subclass: Provider - if subclass._meta.abstract: - continue - data.append( - { - "name": subclass._meta.verbose_name, - "description": subclass.__doc__, - "component": subclass().component, - "model_name": subclass._meta.model_name, - } - ) - data.append( - { - "name": _("SAML Provider from Metadata"), - "description": _("Create a SAML Provider by importing its Metadata."), - "component": "ak-provider-saml-import-form", - "model_name": "", - } - ) - return Response(TypeCreateSerializer(data, many=True).data) diff --git a/authentik/core/api/sources.py b/authentik/core/api/sources.py index eff2c9211d..8349ca084d 100644 --- a/authentik/core/api/sources.py +++ b/authentik/core/api/sources.py @@ -1,5 +1,6 @@ """Source API Views""" -from typing import Iterable + +from collections.abc import Iterable from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import OpenApiResponse, extend_schema @@ -10,15 +11,14 @@ from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import GenericViewSet from structlog.stdlib import get_logger from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions -from authentik.api.decorators import permission_required from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT +from authentik.core.api.object_types import TypesMixin from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer +from authentik.core.api.utils import MetaNameSerializer, ModelSerializer from authentik.core.models import Source, UserSourceConnection from authentik.core.types import UserSettingSerializer from authentik.lib.utils.file import ( @@ -27,8 +27,8 @@ from authentik.lib.utils.file import ( set_file, set_file_url, ) -from authentik.lib.utils.reflection import all_subclasses from authentik.policies.engine import PolicyEngine +from authentik.rbac.decorators import permission_required LOGGER = get_logger() @@ -73,6 +73,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer): class SourceViewSet( + TypesMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, UsedByMixin, @@ -131,30 +132,6 @@ class SourceViewSet( source: Source = self.get_object() return set_file_url(request, source, "icon") - @extend_schema(responses={200: TypeCreateSerializer(many=True)}) - @action(detail=False, pagination_class=None, filter_backends=[]) - def types(self, request: Request) -> Response: - """Get all creatable source types""" - data = [] - for subclass in all_subclasses(self.queryset.model): - subclass: Source - component = "" - if len(subclass.__subclasses__()) > 0: - continue - if subclass._meta.abstract: - component = subclass.__bases__[0]().component - else: - component = subclass().component - data.append( - { - "name": subclass._meta.verbose_name, - "description": subclass.__doc__, - "component": component, - "model_name": subclass._meta.model_name, - } - ) - return Response(TypeCreateSerializer(data, many=True).data) - @extend_schema(responses={200: UserSettingSerializer(many=True)}) @action(detail=False, pagination_class=None, filter_backends=[]) def user_settings(self, request: Request) -> Response: diff --git a/authentik/core/api/tokens.py b/authentik/core/api/tokens.py index e3c2d46bf0..a615f0d30e 100644 --- a/authentik/core/api/tokens.py +++ b/authentik/core/api/tokens.py @@ -1,6 +1,8 @@ """Tokens API Viewset""" + from typing import Any +from django.utils.timezone import now from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer from guardian.shortcuts import assign_perm, get_anonymous_user @@ -10,19 +12,26 @@ from rest_framework.fields import CharField from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet from authentik.api.authorization import OwnerSuperuserPermissions -from authentik.api.decorators import permission_required from authentik.blueprints.api import ManagedSerializer from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.core.api.used_by import UsedByMixin from authentik.core.api.users import UserSerializer -from authentik.core.api.utils import PassiveSerializer -from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents +from authentik.core.api.utils import ModelSerializer, PassiveSerializer +from authentik.core.models import ( + USER_ATTRIBUTE_TOKEN_EXPIRING, + USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME, + Token, + TokenIntents, + User, + default_token_duration, +) from authentik.events.models import Event, EventAction from authentik.events.utils import model_to_dict +from authentik.lib.utils.time import timedelta_from_string +from authentik.rbac.decorators import permission_required class TokenSerializer(ManagedSerializer, ModelSerializer): @@ -48,6 +57,32 @@ class TokenSerializer(ManagedSerializer, ModelSerializer): attrs.setdefault("intent", TokenIntents.INTENT_API) if attrs.get("intent") not in [TokenIntents.INTENT_API, TokenIntents.INTENT_APP_PASSWORD]: raise ValidationError({"intent": f"Invalid intent {attrs.get('intent')}"}) + + if attrs.get("intent") == TokenIntents.INTENT_APP_PASSWORD: + # user IS in attrs + user: User = attrs.get("user") + max_token_lifetime = user.group_attributes(request).get( + USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME, + ) + max_token_lifetime_dt = default_token_duration() + if max_token_lifetime is not None: + try: + max_token_lifetime_dt = now() + timedelta_from_string(max_token_lifetime) + except ValueError: + pass + + if "expires" in attrs and attrs.get("expires") > max_token_lifetime_dt: + raise ValidationError( + { + "expires": ( + f"Token expires exceeds maximum lifetime ({max_token_lifetime_dt} UTC)." + ) + } + ) + elif attrs.get("intent") == TokenIntents.INTENT_API: + # For API tokens, expires cannot be overridden + attrs["expires"] = default_token_duration() + return attrs class Meta: diff --git a/authentik/core/api/transactional_applications.py b/authentik/core/api/transactional_applications.py index 19b6ea4656..1e47096ca6 100644 --- a/authentik/core/api/transactional_applications.py +++ b/authentik/core/api/transactional_applications.py @@ -1,4 +1,5 @@ """transactional application and provider creation""" + from django.apps import apps from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema, extend_schema_field from rest_framework.exceptions import ValidationError @@ -64,7 +65,7 @@ class TransactionApplicationSerializer(PassiveSerializer): raise ValidationError("Invalid provider model") self._provider_model = model except LookupError: - raise ValidationError("Invalid provider model") + raise ValidationError("Invalid provider model") from None return fq_model_name def validate(self, attrs: dict) -> dict: @@ -105,7 +106,7 @@ class TransactionApplicationSerializer(PassiveSerializer): { exc.entry_id: exc.validation_error.detail, } - ) + ) from None return blueprint diff --git a/authentik/core/api/used_by.py b/authentik/core/api/used_by.py index 25e7d82951..3158420c41 100644 --- a/authentik/core/api/used_by.py +++ b/authentik/core/api/used_by.py @@ -1,4 +1,5 @@ """used_by mixin""" + from enum import Enum from inspect import getmembers @@ -31,19 +32,19 @@ class UsedBySerializer(PassiveSerializer): model_name = CharField() pk = CharField() name = CharField() - action = ChoiceField(choices=[(x.name, x.name) for x in DeleteAction]) + action = ChoiceField(choices=[(x.value, x.name) for x in DeleteAction]) def get_delete_action(manager: Manager) -> str: """Get the delete action from the Foreign key, falls back to cascade""" if hasattr(manager, "field"): if manager.field.remote_field.on_delete.__name__ == SET_NULL.__name__: - return DeleteAction.SET_NULL.name + return DeleteAction.SET_NULL.value if manager.field.remote_field.on_delete.__name__ == SET_DEFAULT.__name__: - return DeleteAction.SET_DEFAULT.name + return DeleteAction.SET_DEFAULT.value if hasattr(manager, "source_field"): - return DeleteAction.CASCADE_MANY.name - return DeleteAction.CASCADE.name + return DeleteAction.CASCADE_MANY.value + return DeleteAction.CASCADE.value class UsedByMixin: @@ -53,7 +54,6 @@ class UsedByMixin: responses={200: UsedBySerializer(many=True)}, ) @action(detail=True, pagination_class=None, filter_backends=[]) - # pylint: disable=too-many-locals def used_by(self, request: Request, *args, **kwargs) -> Response: """Get a list of all objects that use this object""" model: Model = self.get_object() diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index 5ee249729f..afa7fc908b 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -1,7 +1,8 @@ """User API Views""" + from datetime import timedelta from json import loads -from typing import Any, Optional +from typing import Any from django.contrib.auth import update_session_auth_hash from django.contrib.sessions.backends.cache import KEY_PREFIX @@ -30,22 +31,15 @@ from drf_spectacular.utils import ( extend_schema_field, inline_serializer, ) -from guardian.shortcuts import get_anonymous_user, get_objects_for_user +from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action -from rest_framework.fields import ( - CharField, - IntegerField, - JSONField, - ListField, - SerializerMethodField, -) +from rest_framework.fields import CharField, IntegerField, ListField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ( BooleanField, DateTimeField, ListSerializer, - ModelSerializer, PrimaryKeyRelatedField, ValidationError, ) @@ -54,10 +48,15 @@ from rest_framework.viewsets import ModelViewSet from structlog.stdlib import get_logger from authentik.admin.api.metrics import CoordinateSerializer -from authentik.api.decorators import permission_required from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT +from authentik.brands.models import Brand from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict +from authentik.core.api.utils import ( + JSONDictField, + LinkSerializer, + ModelSerializer, + PassiveSerializer, +) from authentik.core.middleware import ( SESSION_KEY_IMPERSONATE_ORIGINAL_USER, SESSION_KEY_IMPERSONATE_USER, @@ -77,11 +76,11 @@ from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.models import FlowToken from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner from authentik.flows.views.executor import QS_KEY_TOKEN -from authentik.lib.config import CONFIG +from authentik.lib.avatars import get_avatar +from authentik.rbac.decorators import permission_required from authentik.stages.email.models import EmailStage from authentik.stages.email.tasks import send_mails from authentik.stages.email.utils import TemplateEmailMessage -from authentik.tenants.models import Tenant LOGGER = get_logger() @@ -89,8 +88,8 @@ LOGGER = get_logger() class UserGroupSerializer(ModelSerializer): """Simplified Group Serializer for user's groups""" - attributes = JSONField(required=False) - parent_name = CharField(source="parent.name", read_only=True) + attributes = JSONDictField(required=False) + parent_name = CharField(source="parent.name", read_only=True, allow_null=True) class Meta: model = Group @@ -109,14 +108,34 @@ class UserSerializer(ModelSerializer): """User Serializer""" is_superuser = BooleanField(read_only=True) - avatar = CharField(read_only=True) - attributes = JSONField(validators=[is_dict], required=False) + avatar = SerializerMethodField() + attributes = JSONDictField(required=False) groups = PrimaryKeyRelatedField( - allow_empty=True, many=True, source="ak_groups", queryset=Group.objects.all(), default=list + allow_empty=True, + many=True, + source="ak_groups", + queryset=Group.objects.all().order_by("name"), + default=list, ) - groups_obj = ListSerializer(child=UserGroupSerializer(), read_only=True, source="ak_groups") + groups_obj = SerializerMethodField(allow_null=True) uid = CharField(read_only=True) - username = CharField(max_length=150, validators=[UniqueValidator(queryset=User.objects.all())]) + username = CharField( + max_length=150, + validators=[UniqueValidator(queryset=User.objects.all().order_by("username"))], + ) + + @property + def _should_include_groups(self) -> bool: + request: Request = self.context.get("request", None) + if not request: + return True + return str(request.query_params.get("include_groups", "true")).lower() == "true" + + @extend_schema_field(UserGroupSerializer(many=True)) + def get_groups_obj(self, instance: User) -> list[UserGroupSerializer] | None: + if not self._should_include_groups: + return None + return UserGroupSerializer(instance.ak_groups, many=True).data def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -140,7 +159,7 @@ class UserSerializer(ModelSerializer): self._set_password(instance, password) return instance - def _set_password(self, instance: User, password: Optional[str]): + def _set_password(self, instance: User, password: str | None): """Set password of user if we're in a blueprint context, and if it's an empty string then use an unusable password""" if SERIALIZER_CONTEXT_BLUEPRINT in self.context and password: @@ -150,6 +169,10 @@ class UserSerializer(ModelSerializer): instance.set_unusable_password() instance.save() + def get_avatar(self, user: User) -> str: + """User's avatar, either a http/https URL or a data URI""" + return get_avatar(user, self.context.get("request")) + def validate_path(self, path: str) -> str: """Validate path""" if path[:1] == "/" or path[-1] == "/": @@ -204,12 +227,16 @@ class UserSelfSerializer(ModelSerializer): """User Serializer for information a user can retrieve about themselves""" is_superuser = BooleanField(read_only=True) - avatar = CharField(read_only=True) + avatar = SerializerMethodField() groups = SerializerMethodField() uid = CharField(read_only=True) settings = SerializerMethodField() system_permissions = SerializerMethodField() + def get_avatar(self, user: User) -> str: + """User's avatar, either a http/https URL or a data URI""" + return get_avatar(user, self.context.get("request")) + @extend_schema_field( ListSerializer( child=inline_serializer( @@ -227,15 +254,15 @@ class UserSelfSerializer(ModelSerializer): } def get_settings(self, user: User) -> dict[str, Any]: - """Get user settings with tenant and group settings applied""" + """Get user settings with brand and group settings applied""" return user.group_attributes(self._context["request"]).get("settings", {}) def get_system_permissions(self, user: User) -> list[str]: """Get all system permissions assigned to the user""" return list( - user.user_permissions.filter( - content_type__app_label="authentik_rbac", content_type__model="systempermission" - ).values_list("codename", flat=True) + x.split(".", maxsplit=1)[1] + for x in user.get_all_permissions() + if x.startswith("authentik_rbac") ) class Meta: @@ -336,11 +363,11 @@ class UsersFilter(FilterSet): groups_by_name = ModelMultipleChoiceFilter( field_name="ak_groups__name", to_field_name="name", - queryset=Group.objects.all(), + queryset=Group.objects.all().order_by("name"), ) groups_by_pk = ModelMultipleChoiceFilter( field_name="ak_groups", - queryset=Group.objects.all(), + queryset=Group.objects.all().order_by("name"), ) def filter_attributes(self, queryset, name, value): @@ -348,7 +375,7 @@ class UsersFilter(FilterSet): try: value = loads(value) except ValueError: - raise ValidationError(detail="filter: failed to parse JSON") + raise ValidationError(detail="filter: failed to parse JSON") from None if not isinstance(value, dict): raise ValidationError(detail="filter: value must be key:value mapping") qs = {} @@ -384,18 +411,28 @@ class UserViewSet(UsedByMixin, ModelViewSet): search_fields = ["username", "name", "is_active", "email", "uuid"] filterset_class = UsersFilter - def get_queryset(self): # pragma: no cover - return User.objects.all().exclude(pk=get_anonymous_user().pk) + def get_queryset(self): + base_qs = User.objects.all().exclude_anonymous() + if self.serializer_class(context={"request": self.request})._should_include_groups: + base_qs = base_qs.prefetch_related("ak_groups") + return base_qs - def _create_recovery_link(self) -> tuple[Optional[str], Optional[Token]]: - """Create a recovery link (when the current tenant has a recovery flow set), + @extend_schema( + parameters=[ + OpenApiParameter("include_groups", bool, default=True), + ] + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + def _create_recovery_link(self) -> tuple[str, Token]: + """Create a recovery link (when the current brand has a recovery flow set), that can either be shown to an admin or sent to the user directly""" - tenant: Tenant = self.request._request.tenant + brand: Brand = self.request._request.brand # Check that there is a recovery flow, if not return an error - flow = tenant.flow_recovery + flow = brand.flow_recovery if not flow: - LOGGER.debug("No recovery flow set") - return None, None + raise ValidationError({"non_field_errors": "No recovery flow set."}) user: User = self.get_object() planner = FlowPlanner(flow) planner.allow_empty_flows = True @@ -407,8 +444,9 @@ class UserViewSet(UsedByMixin, ModelViewSet): }, ) except FlowNonApplicableException: - LOGGER.warning("Recovery flow not applicable to user") - return None, None + raise ValidationError( + {"non_field_errors": "Recovery flow not applicable to user"} + ) from None token, __ = FlowToken.objects.update_or_create( identifier=f"{user.uid}-password-reset", defaults={ @@ -523,7 +561,7 @@ class UserViewSet(UsedByMixin, ModelViewSet): 400: OpenApiResponse(description="Bad request"), }, ) - @action(detail=True, methods=["POST"]) + @action(detail=True, methods=["POST"], permission_classes=[]) def set_password(self, request: Request, pk: int) -> Response: """Set password for user""" user: User = self.get_object() @@ -553,16 +591,13 @@ class UserViewSet(UsedByMixin, ModelViewSet): @extend_schema( responses={ "200": LinkSerializer(many=False), - "404": LinkSerializer(many=False), }, + request=None, ) - @action(detail=True, pagination_class=None, filter_backends=[]) + @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) def recovery(self, request: Request, pk: int) -> Response: """Create a temporary link that a user can use to recover their accounts""" link, _ = self._create_recovery_link() - if not link: - LOGGER.debug("Couldn't create token") - return Response({"link": ""}, status=404) return Response({"link": link}) @permission_required("authentik_core.reset_user_password") @@ -577,31 +612,28 @@ class UserViewSet(UsedByMixin, ModelViewSet): ], responses={ "204": OpenApiResponse(description="Successfully sent recover email"), - "404": OpenApiResponse(description="Bad request"), }, + request=None, ) - @action(detail=True, pagination_class=None, filter_backends=[]) + @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) def recovery_email(self, request: Request, pk: int) -> Response: """Create a temporary link that a user can use to recover their accounts""" for_user: User = self.get_object() if for_user.email == "": LOGGER.debug("User doesn't have an email address") - return Response(status=404) + raise ValidationError({"non_field_errors": "User does not have an email address set."}) link, token = self._create_recovery_link() - if not link: - LOGGER.debug("Couldn't create token") - return Response(status=404) # Lookup the email stage to assure the current user can access it stages = get_objects_for_user( request.user, "authentik_stages_email.view_emailstage" ).filter(pk=request.query_params.get("email_stage")) if not stages.exists(): LOGGER.debug("Email stage does not exist/user has no permissions") - return Response(status=404) + raise ValidationError({"non_field_errors": "Email stage does not exist."}) email_stage: EmailStage = stages.first() message = TemplateEmailMessage( subject=_(email_stage.subject), - to=[for_user.email], + to=[(for_user.name, for_user.email)], template_name=email_stage.template, language=for_user.locale(request), template_context={ @@ -621,10 +653,10 @@ class UserViewSet(UsedByMixin, ModelViewSet): "401": OpenApiResponse(description="Access denied"), }, ) - @action(detail=True, methods=["POST"]) + @action(detail=True, methods=["POST"], permission_classes=[]) def impersonate(self, request: Request, pk: int) -> Response: """Impersonate a user""" - if not CONFIG.get_bool("impersonation"): + if not request.tenant.impersonation: LOGGER.debug("User attempted to impersonate", user=request.user) return Response(status=401) if not request.user.has_perm("impersonate"): diff --git a/authentik/core/api/utils.py b/authentik/core/api/utils.py index cf18701978..e4c335f126 100644 --- a/authentik/core/api/utils.py +++ b/authentik/core/api/utils.py @@ -1,9 +1,24 @@ """API Utilities""" + from typing import Any from django.db.models import Model -from rest_framework.fields import CharField, IntegerField, JSONField -from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError +from drf_spectacular.extensions import OpenApiSerializerFieldExtension +from drf_spectacular.plumbing import build_basic_type +from drf_spectacular.types import OpenApiTypes +from rest_framework.fields import ( + CharField, + IntegerField, + JSONField, + SerializerMethodField, +) +from rest_framework.serializers import ModelSerializer as BaseModelSerializer +from rest_framework.serializers import ( + Serializer, + ValidationError, + model_meta, + raise_errors_on_nested_writes, +) def is_dict(value: Any): @@ -13,6 +28,54 @@ def is_dict(value: Any): raise ValidationError("Value must be a dictionary, and not have any duplicate keys.") +class ModelSerializer(BaseModelSerializer): + + def update(self, instance: Model, validated_data): + raise_errors_on_nested_writes("update", self, validated_data) + info = model_meta.get_field_info(instance) + + # Simply set each attribute on the instance, and then save it. + # Note that unlike `.create()` we don't need to treat many-to-many + # relationships as being a special case. During updates we already + # have an instance pk for the relationships to be associated with. + m2m_fields = [] + for attr, value in validated_data.items(): + if attr in info.relations and info.relations[attr].to_many: + m2m_fields.append((attr, value)) + else: + setattr(instance, attr, value) + + instance.save() + + # Note that many-to-many fields are set after updating instance. + # Setting m2m fields triggers signals which could potentially change + # updated instance and we do not want it to collide with .update() + for attr, value in m2m_fields: + field = getattr(instance, attr) + # We can't check for inheritance here as m2m managers are generated dynamically + if field.__class__.__name__ == "RelatedManager": + field.set(value, bulk=False) + else: + field.set(value) + + return instance + + +class JSONDictField(JSONField): + """JSON Field which only allows dictionaries""" + + default_validators = [is_dict] + + +class JSONExtension(OpenApiSerializerFieldExtension): + """Generate API Schema for JSON fields as""" + + target_class = "authentik.core.api.utils.JSONDictField" + + def map_serializer_field(self, auto_schema, direction): + return build_basic_type(OpenApiTypes.OBJECT) + + class PassiveSerializer(Serializer): """Base serializer class which doesn't implement create/update methods""" @@ -26,7 +89,7 @@ class PassiveSerializer(Serializer): class PropertyMappingPreviewSerializer(PassiveSerializer): """Preview how the current user is mapped via the property mappings selected in a provider""" - preview = JSONField(read_only=True) + preview = JSONDictField(read_only=True) class MetaNameSerializer(PassiveSerializer): @@ -49,15 +112,6 @@ class MetaNameSerializer(PassiveSerializer): return f"{obj._meta.app_label}.{obj._meta.model_name}" -class TypeCreateSerializer(PassiveSerializer): - """Types of an object that can be created""" - - name = CharField(required=True) - description = CharField(required=True) - component = CharField(required=True) - model_name = CharField(required=True) - - class CacheSerializer(PassiveSerializer): """Generic cache stats for an object""" diff --git a/authentik/core/apps.py b/authentik/core/apps.py index 719b2abb1c..6fff9cb89f 100644 --- a/authentik/core/apps.py +++ b/authentik/core/apps.py @@ -1,4 +1,5 @@ """authentik core app config""" + from django.conf import settings from authentik.blueprints.apps import ManagedAppConfig @@ -13,18 +14,16 @@ class AuthentikCoreConfig(ManagedAppConfig): mountpoint = "" default = True - def reconcile_load_core_signals(self): - """Load core signals""" - self.import_module("authentik.core.signals") - - def reconcile_debug_worker_hook(self): + @ManagedAppConfig.reconcile_global + def debug_worker_hook(self): """Dispatch startup tasks inline when debugging""" if settings.DEBUG: from authentik.root.celery import worker_ready_hook worker_ready_hook() - def reconcile_source_inbuilt(self): + @ManagedAppConfig.reconcile_tenant + def source_inbuilt(self): """Reconcile inbuilt source""" from authentik.core.models import Source diff --git a/authentik/core/auth.py b/authentik/core/auth.py index a4ede1387d..4eef71556c 100644 --- a/authentik/core/auth.py +++ b/authentik/core/auth.py @@ -1,6 +1,6 @@ """Authenticate with tokens""" -from typing import Any, Optional +from typing import Any from django.contrib.auth.backends import ModelBackend from django.http.request import HttpRequest @@ -16,23 +16,24 @@ class InbuiltBackend(ModelBackend): """Inbuilt backend""" def authenticate( - self, request: HttpRequest, username: Optional[str], password: Optional[str], **kwargs: Any - ) -> Optional[User]: + self, request: HttpRequest, username: str | None, password: str | None, **kwargs: Any + ) -> User | None: user = super().authenticate(request, username=username, password=password, **kwargs) if not user: return None self.set_method("password", request) return user - def set_method(self, method: str, request: Optional[HttpRequest], **kwargs): + def set_method(self, method: str, request: HttpRequest | None, **kwargs): """Set method data on current flow, if possbiel""" if not request: return # Since we can't directly pass other variables to signals, and we want to log the method # and the token used, we assume we're running in a flow and set a variable in the context flow_plan: FlowPlan = request.session.get(SESSION_KEY_PLAN, FlowPlan("")) - flow_plan.context[PLAN_CONTEXT_METHOD] = method - flow_plan.context[PLAN_CONTEXT_METHOD_ARGS] = cleanse_dict(sanitize_dict(kwargs)) + flow_plan.context.setdefault(PLAN_CONTEXT_METHOD, method) + flow_plan.context.setdefault(PLAN_CONTEXT_METHOD_ARGS, {}) + flow_plan.context[PLAN_CONTEXT_METHOD_ARGS].update(cleanse_dict(sanitize_dict(kwargs))) request.session[SESSION_KEY_PLAN] = flow_plan @@ -40,16 +41,18 @@ class TokenBackend(InbuiltBackend): """Authenticate with token""" def authenticate( - self, request: HttpRequest, username: Optional[str], password: Optional[str], **kwargs: Any - ) -> Optional[User]: + self, request: HttpRequest, username: str | None, password: str | None, **kwargs: Any + ) -> User | None: try: + user = User._default_manager.get_by_natural_key(username) + except User.DoesNotExist: # Run the default password hasher once to reduce the timing # difference between an existing and a nonexistent user (#20760). User().set_password(password) return None - # pylint: disable=no-member + tokens = Token.filter_not_expired( user=user, key=password, intent=TokenIntents.INTENT_APP_PASSWORD ) diff --git a/authentik/core/channels.py b/authentik/core/channels.py index 00f213efc3..4b0de49aa7 100644 --- a/authentik/core/channels.py +++ b/authentik/core/channels.py @@ -1,22 +1,30 @@ """Channels base classes""" + +from channels.db import database_sync_to_async from channels.exceptions import DenyConnection -from channels.generic.websocket import JsonWebsocketConsumer from rest_framework.exceptions import AuthenticationFailed from structlog.stdlib import get_logger from authentik.api.authentication import bearer_auth -from authentik.core.models import User LOGGER = get_logger() -class AuthJsonConsumer(JsonWebsocketConsumer): +class TokenOutpostMiddleware: """Authorize a client with a token""" - user: User + def __init__(self, inner): + self.inner = inner - def connect(self): - headers = dict(self.scope["headers"]) + async def __call__(self, scope, receive, send): + scope = dict(scope) + await self.auth(scope) + return await self.inner(scope, receive, send) + + @database_sync_to_async + def auth(self, scope): + """Authenticate request from header""" + headers = dict(scope["headers"]) if b"authorization" not in headers: LOGGER.warning("WS Request without authorization header") raise DenyConnection() @@ -30,6 +38,6 @@ class AuthJsonConsumer(JsonWebsocketConsumer): raise DenyConnection() except AuthenticationFailed as exc: LOGGER.warning("Failed to authenticate", exc=exc) - raise DenyConnection() + raise DenyConnection() from None - self.user = user + scope["user"] = user diff --git a/authentik/core/exceptions.py b/authentik/core/exceptions.py deleted file mode 100644 index 7b157fc269..0000000000 --- a/authentik/core/exceptions.py +++ /dev/null @@ -1,6 +0,0 @@ -"""authentik core exceptions""" -from authentik.lib.sentry import SentryIgnoredException - - -class PropertyMappingExpressionException(SentryIgnoredException): - """Error when a PropertyMapping Exception expression could not be parsed or evaluated.""" diff --git a/authentik/core/expression/evaluator.py b/authentik/core/expression/evaluator.py index 85e6ccbc4d..23ebeffb77 100644 --- a/authentik/core/expression/evaluator.py +++ b/authentik/core/expression/evaluator.py @@ -1,10 +1,13 @@ """Property Mapping Evaluator""" -from typing import Any, Optional + +from types import CodeType +from typing import Any from django.db.models import Model from django.http import HttpRequest from prometheus_client import Histogram +from authentik.core.expression.exceptions import SkipObjectException from authentik.core.models import User from authentik.events.models import Event, EventAction from authentik.lib.expression.evaluator import BaseEvaluator @@ -22,30 +25,43 @@ class PropertyMappingEvaluator(BaseEvaluator): """Custom Evaluator that adds some different context variables.""" dry_run: bool + model: Model + _compiled: CodeType | None = None def __init__( self, model: Model, - user: Optional[User] = None, - request: Optional[HttpRequest] = None, - dry_run: Optional[bool] = False, + user: User | None = None, + request: HttpRequest | None = None, + dry_run: bool | None = False, **kwargs, ): + self.model = model if hasattr(model, "name"): _filename = model.name else: _filename = str(model) super().__init__(filename=_filename) + self.dry_run = dry_run + self.set_context(user, request, **kwargs) + + def set_context( + self, + user: User | None = None, + request: HttpRequest | None = None, + **kwargs, + ): req = PolicyRequest(user=User()) - req.obj = model + req.obj = self.model if user: req.user = user self._context["user"] = user if request: req.http_request = request + req.context.update(**kwargs) self._context["request"] = req self._context.update(**kwargs) - self.dry_run = dry_run + self._globals["SkipObject"] = SkipObjectException def handle_error(self, exc: Exception, expression_source: str): """Exception Handler""" @@ -67,3 +83,9 @@ class PropertyMappingEvaluator(BaseEvaluator): def evaluate(self, *args, **kwargs) -> Any: with PROPERTY_MAPPING_TIME.labels(mapping_name=self._filename).time(): return super().evaluate(*args, **kwargs) + + def compile(self, expression: str | None = None) -> Any: + if not self._compiled: + compiled = super().compile(expression or self.model.expression) + self._compiled = compiled + return self._compiled diff --git a/authentik/core/expression/exceptions.py b/authentik/core/expression/exceptions.py new file mode 100644 index 0000000000..05571e59f4 --- /dev/null +++ b/authentik/core/expression/exceptions.py @@ -0,0 +1,18 @@ +"""authentik core exceptions""" + +from authentik.lib.sentry import SentryIgnoredException + + +class PropertyMappingExpressionException(SentryIgnoredException): + """Error when a PropertyMapping Exception expression could not be parsed or evaluated.""" + + def __init__(self, exc: Exception, mapping) -> None: + super().__init__() + self.exc = exc + self.mapping = mapping + + +class SkipObjectException(PropertyMappingExpressionException): + """Exception which can be raised in a property mapping to skip syncing an object. + Only applies to Property mappings which sync objects, and not on mappings which transitively + apply to a single user""" diff --git a/authentik/core/management/commands/bootstrap_tasks.py b/authentik/core/management/commands/bootstrap_tasks.py index e57f822587..1038796b9a 100644 --- a/authentik/core/management/commands/bootstrap_tasks.py +++ b/authentik/core/management/commands/bootstrap_tasks.py @@ -1,13 +1,21 @@ """Run bootstrap tasks""" -from django.core.management.base import BaseCommand -from authentik.root.celery import _get_startup_tasks +from django.core.management.base import BaseCommand +from django_tenants.utils import get_public_schema_name + +from authentik.root.celery import _get_startup_tasks_all_tenants, _get_startup_tasks_default_tenant +from authentik.tenants.models import Tenant class Command(BaseCommand): """Run bootstrap tasks to ensure certain objects are created""" def handle(self, **options): - tasks = _get_startup_tasks() - for task in tasks: - task() + for task in _get_startup_tasks_default_tenant(): + with Tenant.objects.get(schema_name=get_public_schema_name()): + task() + + for task in _get_startup_tasks_all_tenants(): + for tenant in Tenant.objects.filter(ready=True): + with tenant: + task() diff --git a/authentik/core/management/commands/build_source_docs.py b/authentik/core/management/commands/build_source_docs.py index 1c69d5a5f5..d7174f8bc7 100644 --- a/authentik/core/management/commands/build_source_docs.py +++ b/authentik/core/management/commands/build_source_docs.py @@ -1,4 +1,5 @@ """Build source docs""" + from pathlib import Path from django.core.management.base import BaseCommand diff --git a/authentik/core/management/commands/dev_server.py b/authentik/core/management/commands/dev_server.py index ea53167cdb..e9a6af4fa7 100644 --- a/authentik/core/management/commands/dev_server.py +++ b/authentik/core/management/commands/dev_server.py @@ -1,9 +1,34 @@ """custom runserver command""" + +from typing import TextIO + from daphne.management.commands.runserver import Command as RunServer +from daphne.server import Server + +from authentik.root.signals import post_startup, pre_startup, startup + + +class SignalServer(Server): + """Server which signals back to authentik when it finished starting up""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def ready_callable(): + pre_startup.send(sender=self) + startup.send(sender=self) + post_startup.send(sender=self) + + self.ready_callable = ready_callable class Command(RunServer): """custom runserver command, which doesn't show the misleading django startup message""" - def on_bind(self, server_port): - pass + server_cls = SignalServer + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Redirect standard stdout banner from Daphne into the void + # as there are a couple more steps that happen before startup is fully done + self.stdout = TextIO() diff --git a/authentik/core/management/commands/repair_permissions.py b/authentik/core/management/commands/repair_permissions.py index 242aef45a9..3dfa9b8d7d 100644 --- a/authentik/core/management/commands/repair_permissions.py +++ b/authentik/core/management/commands/repair_permissions.py @@ -1,9 +1,12 @@ """Repair missing permissions""" + from django.apps import apps from django.contrib.auth.management import create_permissions from django.core.management.base import BaseCommand, no_translations from guardian.management import create_anonymous_user +from authentik.tenants.models import Tenant + class Command(BaseCommand): """Repair missing permissions""" @@ -11,7 +14,9 @@ class Command(BaseCommand): @no_translations def handle(self, *args, **options): """Check permissions for all apps""" - for app in apps.get_app_configs(): - self.stdout.write(f"Checking app {app.name} ({app.label})\n") - create_permissions(app, verbosity=0) - create_anonymous_user(None, using="default") + for tenant in Tenant.objects.filter(ready=True): + with tenant: + for app in apps.get_app_configs(): + self.stdout.write(f"Checking app {app.name} ({app.label})\n") + create_permissions(app, verbosity=0) + create_anonymous_user(None, using="default") diff --git a/authentik/core/management/commands/shell.py b/authentik/core/management/commands/shell.py index dcaf70e9c6..6731069ae3 100644 --- a/authentik/core/management/commands/shell.py +++ b/authentik/core/management/commands/shell.py @@ -1,4 +1,5 @@ """authentik shell command""" + import code import platform import sys @@ -15,13 +16,8 @@ from authentik.events.middleware import should_log_model from authentik.events.models import Event, EventAction from authentik.events.utils import model_to_dict -BANNER_TEXT = """### authentik shell ({authentik}) -### Node {node} | Arch {arch} | Python {python} """.format( - node=platform.node(), - python=platform.python_version(), - arch=platform.machine(), - authentik=get_full_version(), -) +BANNER_TEXT = f"""### authentik shell ({get_full_version()}) +### Node {platform.node()} | Arch {platform.machine()} | Python {platform.python_version()} """ class Command(BaseCommand): @@ -85,7 +81,7 @@ class Command(BaseCommand): # If Python code has been passed, execute it and exit. if options["command"]: - # pylint: disable=exec-used + exec(options["command"], namespace) # nosec # noqa return @@ -98,7 +94,7 @@ class Command(BaseCommand): else: try: hook() - except Exception: # pylint: disable=broad-except + except Exception: # Match the behavior of the cpython shell where an error in # sys.__interactivehook__ prints a warning and the exception # and continues. diff --git a/authentik/core/management/commands/worker.py b/authentik/core/management/commands/worker.py index f85c36cc9e..06d32839b2 100644 --- a/authentik/core/management/commands/worker.py +++ b/authentik/core/management/commands/worker.py @@ -1,4 +1,5 @@ """Run worker""" + from sys import exit as sysexit from tempfile import tempdir diff --git a/authentik/core/middleware.py b/authentik/core/middleware.py index 2cda0b0c39..f59b9aa6b7 100644 --- a/authentik/core/middleware.py +++ b/authentik/core/middleware.py @@ -1,6 +1,7 @@ """authentik admin Middleware to impersonate users""" + +from collections.abc import Callable from contextvars import ContextVar -from typing import Callable, Optional from uuid import uuid4 from django.http import HttpRequest, HttpResponse @@ -14,9 +15,9 @@ RESPONSE_HEADER_ID = "X-authentik-id" KEY_AUTH_VIA = "auth_via" KEY_USER = "user" -CTX_REQUEST_ID = ContextVar[Optional[str]](STRUCTLOG_KEY_PREFIX + "request_id", default=None) -CTX_HOST = ContextVar[Optional[str]](STRUCTLOG_KEY_PREFIX + "host", default=None) -CTX_AUTH_VIA = ContextVar[Optional[str]](STRUCTLOG_KEY_PREFIX + KEY_AUTH_VIA, default=None) +CTX_REQUEST_ID = ContextVar[str | None](STRUCTLOG_KEY_PREFIX + "request_id", default=None) +CTX_HOST = ContextVar[str | None](STRUCTLOG_KEY_PREFIX + "host", default=None) +CTX_AUTH_VIA = ContextVar[str | None](STRUCTLOG_KEY_PREFIX + KEY_AUTH_VIA, default=None) class ImpersonateMiddleware: @@ -54,7 +55,7 @@ class RequestIDMiddleware: def __call__(self, request: HttpRequest) -> HttpResponse: if not hasattr(request, "request_id"): request_id = uuid4().hex - setattr(request, "request_id", request_id) + request.request_id = request_id CTX_REQUEST_ID.set(request_id) CTX_HOST.set(request.get_host()) set_tag("authentik.request_id", request_id) @@ -66,7 +67,7 @@ class RequestIDMiddleware: response = self.get_response(request) response[RESPONSE_HEADER_ID] = request.request_id - setattr(response, "ak_context", {}) + response.ak_context = {} response.ak_context["request_id"] = CTX_REQUEST_ID.get() response.ak_context["host"] = CTX_HOST.get() response.ak_context[KEY_AUTH_VIA] = CTX_AUTH_VIA.get() diff --git a/authentik/core/migrations/0012_auto_20201003_1737_squashed_0016_auto_20201202_2234.py b/authentik/core/migrations/0012_auto_20201003_1737_squashed_0016_auto_20201202_2234.py index 8d1d0dd97f..5b8cb38ab2 100644 --- a/authentik/core/migrations/0012_auto_20201003_1737_squashed_0016_auto_20201202_2234.py +++ b/authentik/core/migrations/0012_auto_20201003_1737_squashed_0016_auto_20201202_2234.py @@ -5,6 +5,7 @@ from django.db import migrations, models from django.db.backends.base.schema import BaseDatabaseSchemaEditor import authentik.core.models +from authentik.lib.generators import generate_id def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): @@ -16,6 +17,10 @@ def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): token.save() +def default_token_key(): + return generate_id(60) + + class Migration(migrations.Migration): replaces = [ ("authentik_core", "0012_auto_20201003_1737"), @@ -62,7 +67,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="token", name="key", - field=models.TextField(default=authentik.core.models.default_token_key), + field=models.TextField(default=default_token_key), ), migrations.AlterUniqueTogether( name="token", diff --git a/authentik/core/migrations/0029_provider_backchannel_applications_and_more.py b/authentik/core/migrations/0029_provider_backchannel_applications_and_more.py index f34c59ccbd..d6245df749 100644 --- a/authentik/core/migrations/0029_provider_backchannel_applications_and_more.py +++ b/authentik/core/migrations/0029_provider_backchannel_applications_and_more.py @@ -7,9 +7,10 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor def backport_is_backchannel(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): - from authentik.core.models import BackchannelProvider + from authentik.providers.ldap.models import LDAPProvider + from authentik.providers.scim.models import SCIMProvider - for model in BackchannelProvider.__subclasses__(): + for model in [LDAPProvider, SCIMProvider]: try: for obj in model.objects.only("is_backchannel"): obj.is_backchannel = True diff --git a/authentik/core/migrations/0033_alter_user_options.py b/authentik/core/migrations/0033_alter_user_options.py new file mode 100644 index 0000000000..89893251ae --- /dev/null +++ b/authentik/core/migrations/0033_alter_user_options.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.1 on 2024-01-29 12:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_core", "0032_group_roles"), + ] + + operations = [ + migrations.AlterModelOptions( + name="user", + options={ + "permissions": [ + ("reset_user_password", "Reset Password"), + ("impersonate", "Can impersonate other users"), + ("assign_user_permissions", "Can assign permissions to users"), + ("unassign_user_permissions", "Can unassign permissions from users"), + ("preview_user", "Can preview user data sent to providers"), + ("view_user_applications", "View applications the user has access to"), + ], + "verbose_name": "User", + "verbose_name_plural": "Users", + }, + ), + ] diff --git a/authentik/core/migrations/0034_alter_authenticatedsession_expires_and_more.py b/authentik/core/migrations/0034_alter_authenticatedsession_expires_and_more.py new file mode 100644 index 0000000000..dd62bcf852 --- /dev/null +++ b/authentik/core/migrations/0034_alter_authenticatedsession_expires_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 5.0.2 on 2024-02-29 10:15 + +from django.db import migrations, models + +import authentik.core.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_core", "0033_alter_user_options"), + ("authentik_tenants", "0002_tenant_default_token_duration_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="authenticatedsession", + name="expires", + field=models.DateTimeField(default=None, null=True), + ), + migrations.AlterField( + model_name="token", + name="expires", + field=models.DateTimeField(default=None, null=True), + ), + migrations.AlterField( + model_name="token", + name="key", + field=models.TextField(default=authentik.core.models.default_token_key), + ), + ] diff --git a/authentik/core/migrations/0035_alter_group_options_and_more.py b/authentik/core/migrations/0035_alter_group_options_and_more.py new file mode 100644 index 0000000000..f48091c70c --- /dev/null +++ b/authentik/core/migrations/0035_alter_group_options_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 5.0.4 on 2024-04-15 11:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ("authentik_core", "0034_alter_authenticatedsession_expires_and_more"), + ("authentik_rbac", "0003_alter_systempermission_options"), + ] + + operations = [ + migrations.AlterModelOptions( + name="group", + options={ + "permissions": [ + ("add_user_to_group", "Add user to group"), + ("remove_user_from_group", "Remove user from group"), + ], + "verbose_name": "Group", + "verbose_name_plural": "Groups", + }, + ), + migrations.AddIndex( + model_name="group", + index=models.Index(fields=["name"], name="authentik_c_name_9ba8e4_idx"), + ), + migrations.AddIndex( + model_name="user", + index=models.Index(fields=["last_login"], name="authentik_c_last_lo_f0179a_idx"), + ), + migrations.AddIndex( + model_name="user", + index=models.Index( + fields=["password_change_date"], name="authentik_c_passwor_eec915_idx" + ), + ), + migrations.AddIndex( + model_name="user", + index=models.Index(fields=["uuid"], name="authentik_c_uuid_3dae2f_idx"), + ), + migrations.AddIndex( + model_name="user", + index=models.Index(fields=["path"], name="authentik_c_path_b1f502_idx"), + ), + migrations.AddIndex( + model_name="user", + index=models.Index(fields=["type"], name="authentik_c_type_ecf60d_idx"), + ), + ] diff --git a/authentik/core/models.py b/authentik/core/models.py index 7d11af3d6a..0a991ad709 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -1,5 +1,6 @@ """authentik core models""" -from datetime import timedelta + +from datetime import datetime from hashlib import sha256 from typing import Any, Optional, Self from uuid import uuid4 @@ -14,25 +15,27 @@ from django.http import HttpRequest from django.utils.functional import SimpleLazyObject, cached_property from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ +from django_cte import CTEQuerySet, With +from guardian.conf import settings from guardian.mixins import GuardianUserMixin from model_utils.managers import InheritanceManager from rest_framework.serializers import Serializer from structlog.stdlib import get_logger from authentik.blueprints.models import ManagedModel -from authentik.core.exceptions import PropertyMappingExpressionException +from authentik.core.expression.exceptions import PropertyMappingExpressionException from authentik.core.types import UILoginButton, UserSettingSerializer from authentik.lib.avatars import get_avatar -from authentik.lib.config import CONFIG from authentik.lib.generators import generate_id from authentik.lib.models import ( CreatedUpdatedModel, DomainlessFormattedURLValidator, SerializerModel, ) -from authentik.lib.utils.http import get_client_ip +from authentik.lib.utils.time import timedelta_from_string from authentik.policies.models import PolicyBindingModel -from authentik.root.install_id import get_install_id +from authentik.tenants.models import DEFAULT_TOKEN_DURATION, DEFAULT_TOKEN_LENGTH +from authentik.tenants.utils import get_current_tenant, get_unique_identifier LOGGER = get_logger() USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug" @@ -41,26 +44,44 @@ USER_ATTRIBUTE_EXPIRES = "goauthentik.io/user/expires" USER_ATTRIBUTE_DELETE_ON_LOGOUT = "goauthentik.io/user/delete-on-logout" USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources" USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec +USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME = "goauthentik.io/user/token-maximum-lifetime" # nosec USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username" USER_ATTRIBUTE_CHANGE_NAME = "goauthentik.io/user/can-change-name" USER_ATTRIBUTE_CHANGE_EMAIL = "goauthentik.io/user/can-change-email" USER_PATH_SYSTEM_PREFIX = "goauthentik.io" USER_PATH_SERVICE_ACCOUNT = USER_PATH_SYSTEM_PREFIX + "/service-accounts" +options.DEFAULT_NAMES = options.DEFAULT_NAMES + ( + # used_by API that allows models to specify if they shadow an object + # for example the proxy provider which is built on top of an oauth provider + "authentik_used_by_shadows", +) -options.DEFAULT_NAMES = options.DEFAULT_NAMES + ("authentik_used_by_shadows",) +GROUP_RECURSION_LIMIT = 20 -def default_token_duration(): +def default_token_duration() -> datetime: """Default duration a Token is valid""" - return now() + timedelta(minutes=30) + current_tenant = get_current_tenant() + token_duration = ( + current_tenant.default_token_duration + if hasattr(current_tenant, "default_token_duration") + else DEFAULT_TOKEN_DURATION + ) + return now() + timedelta_from_string(token_duration) -def default_token_key(): +def default_token_key() -> str: """Default token key""" + current_tenant = get_current_tenant() + token_length = ( + current_tenant.default_token_length + if hasattr(current_tenant, "default_token_length") + else DEFAULT_TOKEN_LENGTH + ) # We use generate_id since the chars in the key should be easy # to use in Emails (for verification) and URLs (for recovery) - return generate_id(CONFIG.get_int("default_token_length")) + return generate_id(token_length) class UserTypes(models.TextChoices): @@ -78,6 +99,40 @@ class UserTypes(models.TextChoices): INTERNAL_SERVICE_ACCOUNT = "internal_service_account" +class GroupQuerySet(CTEQuerySet): + def with_children_recursive(self): + """Recursively get all groups that have the current queryset as parents + or are indirectly related.""" + + def make_cte(cte): + """Build the query that ends up in WITH RECURSIVE""" + # Start from self, aka the current query + # Add a depth attribute to limit the recursion + return self.annotate( + relative_depth=models.Value(0, output_field=models.IntegerField()) + ).union( + # Here is the recursive part of the query. cte refers to the previous iteration + # Only select groups for which the parent is part of the previous iteration + # and increase the depth + # Finally, limit the depth + cte.join(Group, group_uuid=cte.col.parent_id) + .annotate( + relative_depth=models.ExpressionWrapper( + cte.col.relative_depth + + models.Value(1, output_field=models.IntegerField()), + output_field=models.IntegerField(), + ) + ) + .filter(relative_depth__lt=GROUP_RECURSION_LIMIT), + all=True, + ) + + # Build the recursive query, see above + cte = With.recursive(make_cte) + # Return the result, as a usable queryset for Group. + return cte.join(Group, group_uuid=cte.col.group_uuid).with_cte(cte) + + class Group(SerializerModel): """Group model which supports a basic hierarchy and has attributes""" @@ -100,6 +155,8 @@ class Group(SerializerModel): ) attributes = models.JSONField(default=dict, blank=True) + objects = GroupQuerySet.as_manager() + @property def serializer(self) -> Serializer: from authentik.core.api.groups import GroupSerializer @@ -118,36 +175,11 @@ class Group(SerializerModel): return user.all_groups().filter(group_uuid=self.group_uuid).exists() def children_recursive(self: Self | QuerySet["Group"]) -> QuerySet["Group"]: - """Recursively get all groups that have this as parent or are indirectly related""" - direct_groups = [] - if isinstance(self, QuerySet): - direct_groups = list(x for x in self.all().values_list("pk", flat=True).iterator()) - else: - direct_groups = [self.pk] - if len(direct_groups) < 1: - return Group.objects.none() - query = """ - WITH RECURSIVE parents AS ( - SELECT authentik_core_group.*, 0 AS relative_depth - FROM authentik_core_group - WHERE authentik_core_group.group_uuid = ANY(%s) - - UNION ALL - - SELECT authentik_core_group.*, parents.relative_depth + 1 - FROM authentik_core_group, parents - WHERE ( - authentik_core_group.group_uuid = parents.parent_id and - parents.relative_depth < 20 - ) - ) - SELECT group_uuid - FROM parents - GROUP BY group_uuid, name - ORDER BY name; - """ - group_pks = [group.pk for group in Group.objects.raw(query, [direct_groups]).iterator()] - return Group.objects.filter(pk__in=group_pks) + """Compatibility layer for Group.objects.with_children_recursive()""" + qs = self + if not isinstance(self, QuerySet): + qs = Group.objects.filter(group_uuid=self.group_uuid) + return qs.with_children_recursive() def __str__(self): return f"Group {self.name}" @@ -159,17 +191,38 @@ class Group(SerializerModel): "parent", ), ) + indexes = [models.Index(fields=["name"])] verbose_name = _("Group") verbose_name_plural = _("Groups") + permissions = [ + ("add_user_to_group", _("Add user to group")), + ("remove_user_from_group", _("Remove user from group")), + ] + + +class UserQuerySet(models.QuerySet): + """User queryset""" + + def exclude_anonymous(self): + """Exclude anonymous user""" + return self.exclude(**{User.USERNAME_FIELD: settings.ANONYMOUS_USER_NAME}) class UserManager(DjangoUserManager): """User manager that doesn't assign is_superuser and is_staff""" + def get_queryset(self): + """Create special user queryset""" + return UserQuerySet(self.model, using=self._db) + def create_user(self, username, email=None, password=None, **extra_fields): """User manager that doesn't assign is_superuser and is_staff""" return self._create_user(username, email, password, **extra_fields) + def exclude_anonymous(self) -> QuerySet: + """Exclude anonymous user""" + return self.get_queryset().exclude_anonymous() + class User(SerializerModel, GuardianUserMixin, AbstractUser): """authentik User model, based on django's contrib auth user model.""" @@ -193,17 +246,15 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): return User._meta.get_field("path").default def all_groups(self) -> QuerySet[Group]: - """Recursively get all groups this user is a member of. - At least one query is done to get the direct groups of the user, with groups - there are at most 3 queries done""" - return Group.children_recursive(self.ak_groups.all()) + """Recursively get all groups this user is a member of.""" + return self.ak_groups.all().with_children_recursive() - def group_attributes(self, request: Optional[HttpRequest] = None) -> dict[str, Any]: + def group_attributes(self, request: HttpRequest | None = None) -> dict[str, Any]: """Get a dictionary containing the attributes from all groups the user belongs to, including the users attributes""" final_attributes = {} - if request and hasattr(request, "tenant"): - always_merger.merge(final_attributes, request.tenant.attributes) + if request and hasattr(request, "brand"): + always_merger.merge(final_attributes, request.brand.attributes) for group in self.all_groups().order_by("name"): always_merger.merge(final_attributes, group.attributes) always_merger.merge(final_attributes, self.attributes) @@ -252,17 +303,17 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): @property def uid(self) -> str: """Generate a globally unique UID, based on the user ID and the hashed secret key""" - return sha256(f"{self.id}-{get_install_id()}".encode("ascii")).hexdigest() + return sha256(f"{self.id}-{get_unique_identifier()}".encode("ascii")).hexdigest() - def locale(self, request: Optional[HttpRequest] = None) -> str: + def locale(self, request: HttpRequest | None = None) -> str: """Get the locale the user has configured""" try: return self.attributes.get("settings", {}).get("locale", "") - # pylint: disable=broad-except + except Exception as exc: LOGGER.warning("Failed to get default locale", exc=exc) if request: - return request.tenant.locale + return request.brand.locale return "" @property @@ -278,6 +329,15 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): ("impersonate", _("Can impersonate other users")), ("assign_user_permissions", _("Can assign permissions to users")), ("unassign_user_permissions", _("Can unassign permissions from users")), + ("preview_user", _("Can preview user data sent to providers")), + ("view_user_applications", _("View applications the user has access to")), + ] + indexes = [ + models.Index(fields=["last_login"]), + models.Index(fields=["password_change_date"]), + models.Index(fields=["uuid"]), + models.Index(fields=["path"]), + models.Index(fields=["type"]), ] @@ -324,11 +384,15 @@ class Provider(SerializerModel): objects = InheritanceManager() @property - def launch_url(self) -> Optional[str]: + def launch_url(self) -> str | None: """URL to this provider and initiate authorization for the user. Can return None for providers that are not URL-based""" return None + @property + def icon_url(self) -> str | None: + return None + @property def component(self) -> str: """Return component used to edit this object""" @@ -401,7 +465,7 @@ class Application(SerializerModel, PolicyBindingModel): return ApplicationSerializer @property - def get_meta_icon(self) -> Optional[str]: + def get_meta_icon(self) -> str | None: """Get the URL to the App Icon image. If the name is /static or starts with http it is returned as-is""" if not self.meta_icon: @@ -410,7 +474,7 @@ class Application(SerializerModel, PolicyBindingModel): return self.meta_icon.name return self.meta_icon.url - def get_launch_url(self, user: Optional["User"] = None) -> Optional[str]: + def get_launch_url(self, user: Optional["User"] = None) -> str | None: """Get launch URL if set, otherwise attempt to get launch URL based on provider.""" url = None if self.meta_launch_url: @@ -423,13 +487,13 @@ class Application(SerializerModel, PolicyBindingModel): user = user._wrapped try: return url % user.__dict__ - # pylint: disable=broad-except + except Exception as exc: LOGGER.warning("Failed to format launch url", exc=exc) return url return url - def get_provider(self) -> Optional[Provider]: + def get_provider(self) -> Provider | None: """Get casted provider instance""" if not self.provider: return None @@ -517,7 +581,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel): objects = InheritanceManager() @property - def icon_url(self) -> Optional[str]: + def icon_url(self) -> str | None: """Get the URL to the Icon. If the name is /static or starts with http it is returned as-is""" if not self.icon: @@ -532,7 +596,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel): return self.user_path_template % { "slug": self.slug, } - # pylint: disable=broad-except + except Exception as exc: LOGGER.warning("Failed to template user path", exc=exc, source=self) return User.default_path() @@ -542,12 +606,12 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel): """Return component used to edit this object""" raise NotImplementedError - def ui_login_button(self, request: HttpRequest) -> Optional[UILoginButton]: + def ui_login_button(self, request: HttpRequest) -> UILoginButton | None: """If source uses a http-based flow, return UI Information about the login button. If source doesn't use http-based flow, return None.""" return None - def ui_user_settings(self) -> Optional[UserSettingSerializer]: + def ui_user_settings(self) -> UserSettingSerializer | None: """Entrypoint to integrate with User settings. Can either return None if no user settings are available, or UserSettingSerializer.""" return None @@ -583,6 +647,9 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel): """Get serializer for this model""" raise NotImplementedError + def __str__(self) -> str: + return f"User-source connection (user={self.user_id}, source={self.source_id})" + class Meta: unique_together = (("user", "source"),) @@ -590,9 +657,12 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel): class ExpiringModel(models.Model): """Base Model which can expire, and is automatically cleaned up.""" - expires = models.DateTimeField(default=default_token_duration) + expires = models.DateTimeField(default=None, null=True) expiring = models.BooleanField(default=True) + class Meta: + abstract = True + def expire_action(self, *args, **kwargs): """Handler which is called when this object is expired. By default the object is deleted. This is less efficient compared @@ -601,7 +671,7 @@ class ExpiringModel(models.Model): return self.delete(*args, **kwargs) @classmethod - def filter_not_expired(cls, **kwargs) -> QuerySet: + def filter_not_expired(cls, **kwargs) -> QuerySet["Token"]: """Filer for tokens which are not expired yet or are not expiring, and match filters in `kwargs`""" for obj in cls.objects.filter(**kwargs).filter(Q(expires__lt=now(), expiring=True)): @@ -615,9 +685,6 @@ class ExpiringModel(models.Model): return False return now() > self.expires - class Meta: - abstract = True - class TokenIntents(models.TextChoices): """Intents a Token can be created for.""" @@ -647,6 +714,21 @@ class Token(SerializerModel, ManagedModel, ExpiringModel): user = models.ForeignKey("User", on_delete=models.CASCADE, related_name="+") description = models.TextField(default="", blank=True) + class Meta: + verbose_name = _("Token") + verbose_name_plural = _("Tokens") + indexes = [ + models.Index(fields=["identifier"]), + models.Index(fields=["key"]), + ] + permissions = [("view_token_key", _("View token's key"))] + + def __str__(self): + description = f"{self.identifier}" + if self.expiring: + description += f" (expires={self.expires})" + return description + @property def serializer(self) -> type[Serializer]: from authentik.core.api.tokens import TokenSerializer @@ -674,21 +756,6 @@ class Token(SerializerModel, ManagedModel, ExpiringModel): message=f"Token {self.identifier}'s secret was rotated.", ).save() - def __str__(self): - description = f"{self.identifier}" - if self.expiring: - description += f" (expires={self.expires})" - return description - - class Meta: - verbose_name = _("Token") - verbose_name_plural = _("Tokens") - indexes = [ - models.Index(fields=["identifier"]), - models.Index(fields=["key"]), - ] - permissions = [("view_token_key", _("View token's key"))] - class PropertyMapping(SerializerModel, ManagedModel): """User-defined key -> x mapping which can be used by providers to expose extra data.""" @@ -709,7 +776,7 @@ class PropertyMapping(SerializerModel, ManagedModel): """Get serializer for this model""" raise NotImplementedError - def evaluate(self, user: Optional[User], request: Optional[HttpRequest], **kwargs) -> Any: + def evaluate(self, user: User | None, request: HttpRequest | None, **kwargs) -> Any: """Evaluate `self.expression` using `**kwargs` as Context.""" from authentik.core.expression.evaluator import PropertyMappingEvaluator @@ -717,7 +784,7 @@ class PropertyMapping(SerializerModel, ManagedModel): try: return evaluator.evaluate(self.expression) except Exception as exc: - raise PropertyMappingExpressionException(exc) from exc + raise PropertyMappingExpressionException(self, exc) from exc def __str__(self): return f"Property Mapping {self.name}" @@ -745,19 +812,24 @@ class AuthenticatedSession(ExpiringModel): last_user_agent = models.TextField(blank=True) last_used = models.DateTimeField(auto_now=True) + class Meta: + verbose_name = _("Authenticated Session") + verbose_name_plural = _("Authenticated Sessions") + + def __str__(self) -> str: + return f"Authenticated Session {self.session_key[:10]}" + @staticmethod def from_request(request: HttpRequest, user: User) -> Optional["AuthenticatedSession"]: """Create a new session from a http request""" + from authentik.root.middleware import ClientIPMiddleware + if not hasattr(request, "session") or not request.session.session_key: return None return AuthenticatedSession( session_key=request.session.session_key, user=user, - last_ip=get_client_ip(request), + last_ip=ClientIPMiddleware.get_client_ip(request), last_user_agent=request.META.get("HTTP_USER_AGENT", ""), expires=request.session.get_expiry_date(), ) - - class Meta: - verbose_name = _("Authenticated Session") - verbose_name_plural = _("Authenticated Sessions") diff --git a/authentik/core/signals.py b/authentik/core/signals.py index 31340fce91..228d59ce4c 100644 --- a/authentik/core/signals.py +++ b/authentik/core/signals.py @@ -1,4 +1,5 @@ """authentik core signals""" + from django.contrib.auth.signals import user_logged_in, user_logged_out from django.contrib.sessions.backends.cache import KEY_PREFIX from django.core.cache import cache @@ -9,7 +10,14 @@ from django.dispatch import receiver from django.http.request import HttpRequest from structlog.stdlib import get_logger -from authentik.core.models import Application, AuthenticatedSession, BackchannelProvider, User +from authentik.core.models import ( + Application, + AuthenticatedSession, + BackchannelProvider, + ExpiringModel, + User, + default_token_duration, +) # Arguments: user: User, password: str password_changed = Signal() @@ -60,3 +68,12 @@ def backchannel_provider_pre_save(sender: type[Model], instance: Model, **_): if not isinstance(instance, BackchannelProvider): return instance.is_backchannel = True + + +@receiver(pre_save) +def expiring_model_pre_save(sender: type[Model], instance: Model, **_): + """Ensure expires is set on ExpiringModels that are set to expire""" + if not issubclass(sender, ExpiringModel): + return + if instance.expiring and instance.expires is None: + instance.expires = default_token_duration() diff --git a/authentik/core/sources/flow_manager.py b/authentik/core/sources/flow_manager.py index 0fb038d909..ce55611d18 100644 --- a/authentik/core/sources/flow_manager.py +++ b/authentik/core/sources/flow_manager.py @@ -1,6 +1,7 @@ """Source decision helper""" + from enum import Enum -from typing import Any, Optional +from typing import Any from django.contrib import messages from django.db import IntegrityError @@ -12,11 +13,12 @@ from django.utils.translation import gettext as _ from structlog.stdlib import get_logger from authentik.core.models import Source, SourceUserMatchingModes, User, UserSourceConnection -from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostUserEnrollmentStage +from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostSourceStage from authentik.events.models import Event, EventAction from authentik.flows.exceptions import FlowNonApplicableException -from authentik.flows.models import Flow, Stage, in_memory_stage +from authentik.flows.models import Flow, FlowToken, Stage, in_memory_stage from authentik.flows.planner import ( + PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_REDIRECT, PLAN_CONTEXT_SOURCE, @@ -34,6 +36,8 @@ from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT from authentik.stages.user_write.stage import PLAN_CONTEXT_USER_PATH +SESSION_KEY_OVERRIDE_FLOW_TOKEN = "authentik/flows/source_override_flow_token" # nosec + class Action(Enum): """Actions that can be decided based on the request @@ -89,16 +93,13 @@ class SourceFlowManager: self._logger = get_logger().bind(source=source, identifier=identifier) self.policy_context = {} - # pylint: disable=too-many-return-statements - def get_action(self, **kwargs) -> tuple[Action, Optional[UserSourceConnection]]: + def get_action(self, **kwargs) -> tuple[Action, UserSourceConnection | None]: # noqa: PLR0911 """decide which action should be taken""" new_connection = self.connection_type(source=self.source, identifier=self.identifier) # When request is authenticated, always link if self.request.user.is_authenticated: new_connection.user = self.request.user new_connection = self.update_connection(new_connection, **kwargs) - # pylint: disable=no-member - new_connection.save() return Action.LINK, new_connection existing_connections = self.connection_type.objects.filter( @@ -145,7 +146,6 @@ class SourceFlowManager: ]: new_connection.user = user new_connection = self.update_connection(new_connection, **kwargs) - new_connection.save() return Action.LINK, new_connection if self.source.user_matching_mode in [ SourceUserMatchingModes.EMAIL_DENY, @@ -187,8 +187,10 @@ class SourceFlowManager: # Default case, assume deny error = Exception( _( - "Request to authenticate with %(source)s has been denied. Please authenticate " - "with the source you've previously signed up with." % {"source": self.source.name} + "Request to authenticate with {source} has been denied. Please authenticate " + "with the source you've previously signed up with.".format_map( + {"source": self.source.name} + ) ), ) return self.error_handler(error) @@ -204,38 +206,55 @@ class SourceFlowManager: def get_stages_to_append(self, flow: Flow) -> list[Stage]: """Hook to override stages which are appended to the flow""" - if not self.source.enrollment_flow: - return [] - if flow.slug == self.source.enrollment_flow.slug: - return [ - in_memory_stage(PostUserEnrollmentStage), - ] - return [] + return [ + in_memory_stage(PostSourceStage), + ] def _prepare_flow( self, flow: Flow, connection: UserSourceConnection, - stages: Optional[list[StageView]] = None, + stages: list[StageView] | None = None, **kwargs, ) -> HttpResponse: """Prepare Authentication Plan, redirect user FlowExecutor""" - # Ensure redirect is carried through when user was trying to - # authorize application - final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get( - NEXT_ARG_NAME, "authentik_core:if-user" - ) kwargs.update( { # Since we authenticate the user by their token, they have no backend set PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT, PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_SOURCE: self.source, - PLAN_CONTEXT_REDIRECT: final_redirect, PLAN_CONTEXT_SOURCES_CONNECTION: connection, } ) kwargs.update(self.policy_context) + if SESSION_KEY_OVERRIDE_FLOW_TOKEN in self.request.session: + token: FlowToken = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN) + self._logger.info("Replacing source flow with overridden flow", flow=token.flow.slug) + plan = token.plan + plan.context[PLAN_CONTEXT_IS_RESTORED] = token + plan.context.update(kwargs) + for stage in self.get_stages_to_append(flow): + plan.append_stage(stage) + if stages: + for stage in stages: + plan.append_stage(stage) + self.request.session[SESSION_KEY_PLAN] = plan + flow_slug = token.flow.slug + token.delete() + return redirect_with_qs( + "authentik_core:if-flow", + self.request.GET, + flow_slug=flow_slug, + ) + # Ensure redirect is carried through when user was trying to + # authorize application + final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get( + NEXT_ARG_NAME, "authentik_core:if-user" + ) + if PLAN_CONTEXT_REDIRECT not in kwargs: + kwargs[PLAN_CONTEXT_REDIRECT] = final_redirect + if not flow: return bad_request_message( self.request, @@ -243,6 +262,9 @@ class SourceFlowManager: ) # We run the Flow planner here so we can pass the Pending user in the context planner = FlowPlanner(flow) + # We append some stages so the initial flow we get might be empty + planner.allow_empty_flows = True + planner.use_cache = False plan = planner.plan(self.request, kwargs) for stage in self.get_stages_to_append(flow): plan.append_stage(stage) @@ -269,7 +291,9 @@ class SourceFlowManager: in_memory_stage( MessageStage, message=_( - "Successfully authenticated with %(source)s!" % {"source": self.source.name} + "Successfully authenticated with {source}!".format_map( + {"source": self.source.name} + ) ), ) ], @@ -293,13 +317,13 @@ class SourceFlowManager: ).from_http(self.request) messages.success( self.request, - _("Successfully linked %(source)s!" % {"source": self.source.name}), + _("Successfully linked {source}!".format_map({"source": self.source.name})), ) return redirect( reverse( "authentik_core:if-user", ) - + f"#/settings;page-{self.source.slug}" + + "#/settings;page-sources" ) def handle_enroll( @@ -321,7 +345,9 @@ class SourceFlowManager: in_memory_stage( MessageStage, message=_( - "Successfully authenticated with %(source)s!" % {"source": self.source.name} + "Successfully authenticated with {source}!".format_map( + {"source": self.source.name} + ) ), ) ], diff --git a/authentik/core/sources/stage.py b/authentik/core/sources/stage.py index 385db38922..de863f1982 100644 --- a/authentik/core/sources/stage.py +++ b/authentik/core/sources/stage.py @@ -1,4 +1,5 @@ """Source flow manager stages""" + from django.http import HttpRequest, HttpResponse from authentik.core.models import User, UserSourceConnection @@ -9,7 +10,7 @@ from authentik.flows.stage import StageView PLAN_CONTEXT_SOURCES_CONNECTION = "goauthentik.io/sources/connection" -class PostUserEnrollmentStage(StageView): +class PostSourceStage(StageView): """Dynamically injected stage which saves the Connection after the user has been enrolled.""" @@ -20,10 +21,12 @@ class PostUserEnrollmentStage(StageView): ] user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] connection.user = user + linked = connection.pk is None connection.save() - Event.new( - EventAction.SOURCE_LINKED, - message="Linked Source", - source=connection.source, - ).from_http(self.request) + if linked: + Event.new( + EventAction.SOURCE_LINKED, + message="Linked Source", + source=connection.source, + ).from_http(self.request) return self.executor.stage_ok() diff --git a/authentik/core/tasks.py b/authentik/core/tasks.py index 0b4c839e89..1a565d8f7f 100644 --- a/authentik/core/tasks.py +++ b/authentik/core/tasks.py @@ -1,7 +1,10 @@ """authentik core tasks""" + from datetime import datetime, timedelta +from django.conf import ImproperlyConfigured from django.contrib.sessions.backends.cache import KEY_PREFIX +from django.contrib.sessions.backends.db import SessionStore as DBSessionStore from django.core.cache import cache from django.utils.timezone import now from structlog.stdlib import get_logger @@ -13,20 +16,16 @@ from authentik.core.models import ( ExpiringModel, User, ) -from authentik.events.monitored_tasks import ( - MonitoredTask, - TaskResult, - TaskResultStatus, - prefill_task, -) +from authentik.events.system_tasks import SystemTask, TaskStatus, prefill_task +from authentik.lib.config import CONFIG from authentik.root.celery import CELERY_APP LOGGER = get_logger() -@CELERY_APP.task(bind=True, base=MonitoredTask) +@CELERY_APP.task(bind=True, base=SystemTask) @prefill_task -def clean_expired_models(self: MonitoredTask): +def clean_expired_models(self: SystemTask): """Remove expired objects""" messages = [] for cls in ExpiringModel.__subclasses__(): @@ -41,25 +40,42 @@ def clean_expired_models(self: MonitoredTask): messages.append(f"Expired {amount} {cls._meta.verbose_name_plural}") # Special case amount = 0 + for session in AuthenticatedSession.objects.all(): - cache_key = f"{KEY_PREFIX}{session.session_key}" - value = None - try: - value = cache.get(cache_key) - # pylint: disable=broad-except - except Exception as exc: - LOGGER.debug("Failed to get session from cache", exc=exc) - if not value: - session.delete() - amount += 1 + match CONFIG.get("session_storage", "cache"): + case "cache": + cache_key = f"{KEY_PREFIX}{session.session_key}" + value = None + try: + value = cache.get(cache_key) + + except Exception as exc: + LOGGER.debug("Failed to get session from cache", exc=exc) + if not value: + session.delete() + amount += 1 + case "db": + if not ( + DBSessionStore.get_model_class() + .objects.filter(session_key=session.session_key, expire_date__gt=now()) + .exists() + ): + session.delete() + amount += 1 + case _: + # Should never happen, as we check for other values in authentik/root/settings.py + raise ImproperlyConfigured( + "Invalid session_storage setting, allowed values are db and cache" + ) LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount) + messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}") - self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages)) + self.set_status(TaskStatus.SUCCESSFUL, *messages) -@CELERY_APP.task(bind=True, base=MonitoredTask) +@CELERY_APP.task(bind=True, base=SystemTask) @prefill_task -def clean_temporary_users(self: MonitoredTask): +def clean_temporary_users(self: SystemTask): """Remove temporary users created by SAML Sources""" _now = datetime.now() messages = [] @@ -75,4 +91,4 @@ def clean_temporary_users(self: MonitoredTask): user.delete() deleted_users += 1 messages.append(f"Successfully deleted {deleted_users} users.") - self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages)) + self.set_status(TaskStatus.SUCCESSFUL, *messages) diff --git a/authentik/core/templates/base/header_js.html b/authentik/core/templates/base/header_js.html index 2d15718287..a67a0b3dae 100644 --- a/authentik/core/templates/base/header_js.html +++ b/authentik/core/templates/base/header_js.html @@ -5,7 +5,7 @@ window.authentik = { locale: "{{ LANGUAGE_CODE }}", config: JSON.parse('{{ config_json|escapejs }}'), - tenant: JSON.parse('{{ tenant_json|escapejs }}'), + brand: JSON.parse('{{ brand_json|escapejs }}'), versionFamily: "{{ version_family }}", versionSubdomain: "{{ version_subdomain }}", build: "{{ build }}", diff --git a/authentik/core/templates/base/skeleton.html b/authentik/core/templates/base/skeleton.html index 85137cc428..18413f1eaf 100644 --- a/authentik/core/templates/base/skeleton.html +++ b/authentik/core/templates/base/skeleton.html @@ -1,5 +1,6 @@ {% load static %} {% load i18n %} +{% load authentik_core %} @@ -7,15 +8,15 @@ - {% block title %}{% trans title|default:tenant.branding_title %}{% endblock %} - - + {% block title %}{% trans title|default:brand.branding_title %}{% endblock %} + + {% block head_before %} {% endblock %} - - + {% versioned_script "dist/poly-%v.js" %} + {% versioned_script "dist/standalone/loading/index-%v.js" %} {% block head %} {% endblock %} diff --git a/authentik/core/templates/if/admin.html b/authentik/core/templates/if/admin.html index b3b761f884..9a548dfd9d 100644 --- a/authentik/core/templates/if/admin.html +++ b/authentik/core/templates/if/admin.html @@ -1,9 +1,9 @@ {% extends "base/skeleton.html" %} -{% load static %} +{% load authentik_core %} {% block head %} - +{% versioned_script "dist/admin/AdminInterface-%v.js" %} {% include "base/header_js.html" %} diff --git a/authentik/core/templates/if/end_session.html b/authentik/core/templates/if/end_session.html index e6bfccb1b8..88cb345a14 100644 --- a/authentik/core/templates/if/end_session.html +++ b/authentik/core/templates/if/end_session.html @@ -4,7 +4,7 @@ {% load i18n %} {% block title %} -{% trans 'End session' %} - {{ tenant.branding_title }} +{% trans 'End session' %} - {{ brand.branding_title }} {% endblock %} {% block card_title %} @@ -16,7 +16,7 @@ You've logged out of {{ application }}. {% block card %}

- {% blocktrans with application=application.name branding_title=tenant.branding_title %} + {% blocktrans with application=application.name branding_title=brand.branding_title %} You've logged out of {{ application }}. You can go back to the overview to launch another application, or log out of your {{ branding_title }} account. {% endblocktrans %}

@@ -26,7 +26,7 @@ You've logged out of {{ application }}. - {% blocktrans with branding_title=tenant.branding_title %} + {% blocktrans with branding_title=brand.branding_title %} Log out of {{ branding_title }} {% endblocktrans %} diff --git a/authentik/core/templates/if/error.html b/authentik/core/templates/if/error.html index 9470f02c6c..6abc12e3e9 100644 --- a/authentik/core/templates/if/error.html +++ b/authentik/core/templates/if/error.html @@ -4,7 +4,7 @@ {% load i18n %} {% block title %} -{{ tenant.branding_title }} +{{ brand.branding_title }} {% endblock %} {% block card_title %} diff --git a/authentik/core/templates/if/flow.html b/authentik/core/templates/if/flow.html index 197c3ffdaa..a5b3d7f592 100644 --- a/authentik/core/templates/if/flow.html +++ b/authentik/core/templates/if/flow.html @@ -1,6 +1,7 @@ {% extends "base/skeleton.html" %} {% load static %} +{% load authentik_core %} {% block head_before %} {{ block.super }} @@ -17,7 +18,7 @@ window.authentik.flow = { {% endblock %} {% block head %} - +{% versioned_script "dist/flow/FlowInterface-%v.js" %} + + + + + + diff --git a/web/authentik/sources/rac.svg b/web/authentik/sources/rac.svg new file mode 100644 index 0000000000..6b78fd3968 --- /dev/null +++ b/web/authentik/sources/rac.svg @@ -0,0 +1 @@ +desktop diff --git a/web/authentik/sources/radius.svg b/web/authentik/sources/radius.svg new file mode 100644 index 0000000000..9aca0e6b97 --- /dev/null +++ b/web/authentik/sources/radius.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/web/authentik/sources/saml.png b/web/authentik/sources/saml.png new file mode 100644 index 0000000000..81185a3304 Binary files /dev/null and b/web/authentik/sources/saml.png differ diff --git a/web/authentik/sources/scim.png b/web/authentik/sources/scim.png new file mode 100644 index 0000000000..1c337bcb7f Binary files /dev/null and b/web/authentik/sources/scim.png differ diff --git a/web/build.mjs b/web/build.mjs new file mode 100644 index 0000000000..e945fc19e6 --- /dev/null +++ b/web/build.mjs @@ -0,0 +1,177 @@ +import { execFileSync } from "child_process"; +import * as chokidar from "chokidar"; +import esbuild from "esbuild"; +import fs from "fs"; +import { globSync } from "glob"; +import path from "path"; +import { cwd } from "process"; +import process from "process"; +import { fileURLToPath } from "url"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); + +let authentikProjectRoot = __dirname + "../"; +try { + // Use the package.json file in the root folder, as it has the current version information. + authentikProjectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], { + encoding: "utf8", + }).replace("\n", ""); +} catch (exc) { + // We probably don't have a .git folder, which could happen in container builds +} +const rootPackage = JSON.parse(fs.readFileSync(path.join(authentikProjectRoot, "./package.json"))); + +// eslint-disable-next-line no-undef +const isProdBuild = process.env.NODE_ENV === "production"; + +// eslint-disable-next-line no-undef +const apiBasePath = process.env.AK_API_BASE_PATH || ""; + +const envGitHashKey = "GIT_BUILD_HASH"; + +const definitions = { + "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"), + "process.env.CWD": JSON.stringify(cwd()), + "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath), +}; + +// All is magic is just to make sure the assets are copied into the right places. This is a very stripped down version +// of what the rollup-copy-plugin does, without any of the features we don't use, and using globSync instead of globby +// since we already had globSync lying around thanks to Typescript. If there's a third argument in an array entry, it's +// used to replace the internal path before concatenating it all together as the destination target. + +const otherFiles = [ + ["node_modules/@patternfly/patternfly/patternfly.min.css", "."], + ["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"], + ["src/custom.css", "."], + ["src/common/styles/**", "."], + ["src/assets/images/**", "./assets/images"], + ["./icons/*", "./assets/icons"], +]; + +const isFile = (filePath) => fs.statSync(filePath).isFile(); +function nameCopyTarget(src, dest, strip) { + const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base); + return [src, target]; +} + +for (const [source, rawdest, strip] of otherFiles) { + const matchedPaths = globSync(source); + const dest = path.join("dist", rawdest); + const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip)); + for (const [src, dest] of copyTargets) { + if (isFile(src)) { + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.copyFileSync(src, dest); + } + } +} + +// This starts the definitions used for esbuild: Our targets, our arguments, the function for running a build, and three +// options for building: watching, building, and building the proxy. +// Ordered by largest to smallest interface to build even faster +const interfaces = [ + ["admin/AdminInterface/AdminInterface.ts", "admin"], + ["user/UserInterface.ts", "user"], + ["flow/FlowInterface.ts", "flow"], + ["standalone/api-browser/index.ts", "standalone/api-browser"], + ["enterprise/rac/index.ts", "enterprise/rac"], + ["standalone/loading/index.ts", "standalone/loading"], + ["polyfill/poly.ts", "."], +]; + +const baseArgs = { + bundle: true, + write: true, + sourcemap: true, + minify: isProdBuild, + splitting: true, + treeShaking: true, + external: ["*.woff", "*.woff2"], + tsconfig: "./tsconfig.json", + loader: { ".css": "text", ".md": "text" }, + define: definitions, + format: "esm", +}; + +function getVersion() { + let version = rootPackage.version; + if (process.env[envGitHashKey]) { + version = `${version}.${process.env[envGitHashKey]}`; + } + return version; +} + +async function buildOneSource(source, dest) { + const DIST = path.join(__dirname, "./dist", dest); + // eslint-disable-next-line no-console + console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`); + + try { + const start = Date.now(); + await esbuild.build({ + ...baseArgs, + entryPoints: [`./src/${source}`], + entryNames: `[dir]/[name]-${getVersion()}`, + outdir: DIST, + }); + const end = Date.now(); + // eslint-disable-next-line no-console + console.log( + `[${new Date(end).toISOString()}] Finished build for target ${source} in ${Date.now() - start}ms`, + ); + } catch (exc) { + console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`); + } +} + +async function buildAuthentik(interfaces) { + await Promise.allSettled(interfaces.map(([source, dest]) => buildOneSource(source, dest))); +} + +let timeoutId = null; +function debouncedBuild() { + if (timeoutId !== null) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + // eslint-disable-next-line no-console + console.clear(); + buildAuthentik(interfaces); + }, 250); +} + +if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) { + // eslint-disable-next-line no-console + console.log(`Build the authentikUI + +options: + -w, --watch: Build all ${interfaces.length} interfaces + -p, --proxy: Build only the polyfills and the loading application + -h, --help: This help message +`); + process.exit(0); +} + +if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) { + // eslint-disable-next-line no-console + console.log("Watching ./src for changes"); + chokidar.watch("./src").on("all", (event, path) => { + if (!["add", "change", "unlink"].includes(event)) { + return; + } + if (!/(\.css|\.ts|\.js)$/.test(path)) { + return; + } + debouncedBuild(); + }); +} else if (process.argv.length > 2 && (process.argv[2] === "-p" || process.argv[2] === "--proxy")) { + // There's no watch-for-proxy, sorry. + await buildAuthentik( + interfaces.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)), + ); + process.exit(0); +} else { + // And the fallback: just build it. + await buildAuthentik(interfaces); +} diff --git a/web/docs/Changelog.md b/web/docs/Changelog.md new file mode 100644 index 0000000000..cba5b39992 --- /dev/null +++ b/web/docs/Changelog.md @@ -0,0 +1,17 @@ +### 2024-03-26T09:25:06-0700 + +Split the tsconfig file into a base and build variant. + +Lesson: This lesson is stored here and not in a comment in tsconfig.json because +JSON doesn't like comments. Doug Crockford's purity requirement has doomed an +entire generation to keeping its human-facing meta somewhere other than in the +file where it belongs. + +Lesson: The `extend` command of tsconfig has an unexpected behavior. It is +neither a merge or a replace, but some mixture of the two. The buildfile's +`compilerOptions` is not a full replacement; instead, each of _its_ top-level +fields is a replacement for what is found in the basefile. So while you don't +need to include _everything_ in a `compilerOptions` field if you want to change +one thing, if you want to modify _one_ path in `compilerOptions.path`, you must +include the entire `compilerOptions.path` collection in your buildfile. +g diff --git a/web/lit-localize.json b/web/lit-localize.json index 19a901a919..3c9564de85 100644 --- a/web/lit-localize.json +++ b/web/lit-localize.json @@ -3,15 +3,18 @@ "sourceLocale": "en", "targetLocales": [ "en", - "pseudo-LOCALE", - "fr", - "tr", + "de", "es", + "fr", + "ko", + "nl", "pl", - "zh_TW", + "tr", "zh-Hans", "zh-Hant", - "de" + "zh-CN", + "zh_TW", + "pseudo-LOCALE" ], "tsConfig": "./tsconfig.json", "output": { diff --git a/web/package-lock.json b/web/package-lock.json deleted file mode 100644 index 6621a19ad1..0000000000 --- a/web/package-lock.json +++ /dev/null @@ -1,18815 +0,0 @@ -{ - "name": "@goauthentik/web", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@goauthentik/web", - "version": "0.0.0", - "license": "MIT", - "dependencies": { - "@codemirror/lang-html": "^6.4.7", - "@codemirror/lang-javascript": "^6.2.1", - "@codemirror/lang-python": "^6.1.3", - "@codemirror/lang-xml": "^6.0.2", - "@codemirror/legacy-modes": "^6.3.3", - "@codemirror/theme-one-dark": "^6.1.2", - "@formatjs/intl-listformat": "^7.5.3", - "@fortawesome/fontawesome-free": "^6.5.1", - "@goauthentik/api": "^2023.10.4-1701882394", - "@lit-labs/context": "^0.4.0", - "@lit-labs/task": "^3.1.0", - "@lit/localize": "^0.11.4", - "@open-wc/lit-helpers": "^0.6.0", - "@patternfly/elements": "^2.4.0", - "@patternfly/patternfly": "^4.224.2", - "@sentry/browser": "^7.88.0", - "@sentry/tracing": "^7.88.0", - "@webcomponents/webcomponentsjs": "^2.8.0", - "base64-js": "^1.5.1", - "chart.js": "^4.4.1", - "chartjs-adapter-moment": "^1.0.1", - "codemirror": "^6.0.1", - "construct-style-sheets-polyfill": "^3.1.0", - "core-js": "^3.34.0", - "country-flag-icons": "^1.5.9", - "fuse.js": "^7.0.0", - "lit": "^2.8.0", - "mermaid": "^10.6.1", - "oidc-client-ts": "^2.4.0", - "rapidoc": "^9.3.4", - "style-mod": "^4.1.0", - "webcomponent-qr-code": "^1.2.0", - "yaml": "^2.3.4" - }, - "devDependencies": { - "@babel/core": "^7.23.6", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-decorators": "^7.23.6", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-runtime": "^7.23.6", - "@babel/preset-env": "^7.23.6", - "@babel/preset-typescript": "^7.23.3", - "@hcaptcha/types": "^1.0.3", - "@jackfranklin/rollup-plugin-markdown": "^0.4.0", - "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", - "@lit/localize-tools": "^0.7.1", - "@rollup/plugin-babel": "^6.0.4", - "@rollup/plugin-commonjs": "^25.0.7", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.5", - "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^11.1.5", - "@storybook/addon-essentials": "^7.6.5", - "@storybook/addon-links": "^7.6.5", - "@storybook/api": "^7.6.5", - "@storybook/blocks": "^7.6.4", - "@storybook/manager-api": "^7.6.5", - "@storybook/web-components": "^7.6.5", - "@storybook/web-components-vite": "^7.6.5", - "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@types/chart.js": "^2.9.41", - "@types/codemirror": "5.60.15", - "@types/grecaptcha": "^3.0.7", - "@typescript-eslint/eslint-plugin": "^6.14.0", - "@typescript-eslint/parser": "^6.14.0", - "babel-plugin-macros": "^3.1.0", - "babel-plugin-tsconfig-paths": "^1.0.3", - "cross-env": "^7.0.3", - "eslint": "^8.56.0", - "eslint-config-google": "^0.14.0", - "eslint-plugin-custom-elements": "0.0.8", - "eslint-plugin-lit": "^1.11.0", - "eslint-plugin-sonarjs": "^0.23.0", - "eslint-plugin-storybook": "^0.6.15", - "lit-analyzer": "^2.0.2", - "npm-run-all": "^4.1.5", - "prettier": "^3.1.1", - "pseudolocale": "^2.0.0", - "pyright": "=1.1.338", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "rollup": "^4.9.1", - "rollup-plugin-copy": "^3.5.0", - "rollup-plugin-cssimport": "^1.0.3", - "rollup-plugin-modify": "^3.0.0", - "rollup-plugin-postcss-lit": "^2.1.0", - "storybook": "^7.6.5", - "storybook-addon-mock": "^4.3.0", - "ts-lit-plugin": "^2.0.1", - "tslib": "^2.6.2", - "turnstile-types": "^1.2.0", - "typescript": "^5.3.3", - "vite-tsconfig-paths": "^4.2.2" - }, - "engines": { - "node": ">=20" - }, - "optionalDependencies": { - "@esbuild/darwin-arm64": "^0.19.9", - "@esbuild/linux-amd64": "^0.18.11", - "@esbuild/linux-arm64": "^0.19.9" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@apitools/openapi-parser": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@apitools/openapi-parser/-/openapi-parser-0.0.30.tgz", - "integrity": "sha512-e8KttEjBSozuSO7IVeFTRvzqgsbxwFtGbwc1Yi/u8EgzDqtVpTOgZ5qfSwtzAdKNkx0x+oi+s/1imCAju0lhTA==", - "dependencies": { - "swagger-client": "^3.18.5" - } - }, - "node_modules/@aw-web-design/x-default-browser": { - "version": "1.4.126", - "resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.126.tgz", - "integrity": "sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==", - "dev": true, - "dependencies": { - "default-browser-id": "3.0.0" - }, - "bin": { - "x-default-browser": "bin/x-default-browser.js" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", - "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.6", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", - "@babel/types": "^7.23.6", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.6.tgz", - "integrity": "sha512-cBXU1vZni/CpGF29iTu4YRbOZt3Wat6zCoMDxRF1MayiEc4URxOj31tT65HUM0CRpMowA3HCJaAOVOUnMf96cw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", - "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", - "@babel/types": "^7.23.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", - "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", - "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.23.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", - "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.6.tgz", - "integrity": "sha512-D7Ccq9LfkBFnow3azZGJvZYgcfeqAw3I1e5LoTpj6UKIFQilh8yqXsIGcRIqbBdsPWIz+Ze7ZZfggSj62Qp+Fg==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/plugin-syntax-decorators": "^7.23.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.23.3.tgz", - "integrity": "sha512-cf7Niq4/+/juY67E0PbgH0TDhLQ5J7zS8C/Q5FFx+DWyrRa9sUQdTXkjqKu8zGvuqr7vw1muKiukseihU+PJDA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.23.3.tgz", - "integrity": "sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", - "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", - "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", - "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", - "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", - "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", - "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", - "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", - "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", - "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", - "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", - "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", - "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", - "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", - "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", - "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz", - "integrity": "sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-flow": "^7.23.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", - "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", - "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", - "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", - "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", - "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", - "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", - "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", - "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", - "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", - "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", - "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", - "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", - "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", - "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.23.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", - "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", - "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", - "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", - "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", - "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", - "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", - "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", - "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", - "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.6.tgz", - "integrity": "sha512-kF1Zg62aPseQ11orDhFRw+aPG/eynNQtI+TyY+m33qJa2cJ5EEvza2P2BNTIA9E5MyqFABHEyY6CPHwgdy9aNg==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", - "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", - "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", - "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", - "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", - "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.3.tgz", - "integrity": "sha512-ogV0yWnq38CFwH20l2Afz0dfKuZBx9o/Y2Rmh5vuSS0YD1hswgEgTfyTzuSrT2q9btmHRSqYoSfwFUVaC1M1Jw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.23.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", - "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", - "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", - "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", - "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", - "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.4", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.5", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.3", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.23.4", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-flow": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.23.3.tgz", - "integrity": "sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-flow-strip-types": "^7.23.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", - "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-typescript": "^7.23.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.22.15.tgz", - "integrity": "sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@babel/register/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/register/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, - "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.22.15.tgz", - "integrity": "sha512-SAj8oKi8UogVi6eXQXKNPu8qZ78Yzy7zawrlTr0M+IuW/g8Qe9gVDhGcF9h1S69OyACpYoLxEzpjs1M15sI5wQ==", - "dependencies": { - "core-js-pure": "^3.30.2", - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@braintree/sanitize-url": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", - "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" - }, - "node_modules/@codemirror/autocomplete": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.9.0.tgz", - "integrity": "sha512-Fbwm0V/Wn3BkEJZRhr0hi5BhCo5a7eBL6LYaliPjOSwCyfOpnjXY59HruSxOUNV+1OYer0Tgx1zRNQttjXyDog==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.6.0", - "@lezer/common": "^1.0.0" - }, - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/commands": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.5.tgz", - "integrity": "sha512-dSi7ow2P2YgPBZflR9AJoaTHvqmeGIgkhignYMd5zK5y6DANTvxKxp6eMEpIDUJkRAaOY/TFZ4jP1ADIO/GLVA==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.2.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-css": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.1.tgz", - "integrity": "sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.2", - "@lezer/css": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-html": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.7.tgz", - "integrity": "sha512-y9hWSSO41XlcL4uYwWyk0lEgTHcelWWfRuqmvcAmxfCs0HNWZdriWo/EU43S63SxEZpc1Hd50Itw7ktfQvfkUg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/lang-css": "^6.0.0", - "@codemirror/lang-javascript": "^6.0.0", - "@codemirror/language": "^6.4.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0", - "@lezer/css": "^1.1.0", - "@lezer/html": "^1.3.0" - } - }, - "node_modules/@codemirror/lang-javascript": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.1.tgz", - "integrity": "sha512-jlFOXTejVyiQCW3EQwvKH0m99bUYIw40oPmFjSX2VS78yzfe0HELZ+NEo9Yfo1MkGRpGlj3Gnu4rdxV1EnAs5A==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.6.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0", - "@lezer/javascript": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-python": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.1.3.tgz", - "integrity": "sha512-S9w2Jl74hFlD5nqtUMIaXAq9t5WlM0acCkyuQWUUSvZclk1sV+UfnpFiZzuZSG+hfEaOmxKR5UxY/Uxswn7EhQ==", - "dependencies": { - "@codemirror/autocomplete": "^6.3.2", - "@codemirror/language": "^6.8.0", - "@lezer/python": "^1.1.4" - } - }, - "node_modules/@codemirror/lang-xml": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.2.tgz", - "integrity": "sha512-JQYZjHL2LAfpiZI2/qZ/qzDuSqmGKMwyApYmEUUCTxLM4MWS7sATUEfIguZQr9Zjx/7gcdnewb039smF6nC2zw==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.4.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/xml": "^1.0.0" - } - }, - "node_modules/@codemirror/language": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.9.0.tgz", - "integrity": "sha512-nFu311/0ne/qGuGCL3oKuktBgzVOaxCHZPZv1tLSZkNjPYxxvkjSbzno3MlErG2tgw1Yw1yF8BxMCegeMXqpiw==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" - } - }, - "node_modules/@codemirror/legacy-modes": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.3.3.tgz", - "integrity": "sha512-X0Z48odJ0KIoh/HY8Ltz75/4tDYc9msQf1E/2trlxFaFFhgjpVHjZ/BCXe1Lk7s4Gd67LL/CeEEHNI+xHOiESg==", - "dependencies": { - "@codemirror/language": "^6.0.0" - } - }, - "node_modules/@codemirror/lint": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.4.1.tgz", - "integrity": "sha512-2Hx945qKX7FBan5/gUdTM8fsMYrNG9clIgEcPXestbLVFAUyQYFAuju/5BMNf/PwgpVaX5pvRm4+ovjbp9D9gQ==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "node_modules/@codemirror/search": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.2.tgz", - "integrity": "sha512-WRihpqd0l9cEh9J3IZe45Yi+Z5MfTsEXnyc3V7qXHP4ZYtIYpGOn+EJ7fyLIkyAm/8S6QIr7/mMISfAadf8zCg==", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "node_modules/@codemirror/state": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz", - "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==" - }, - "node_modules/@codemirror/theme-one-dark": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz", - "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/highlight": "^1.0.0" - } - }, - "node_modules/@codemirror/view": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.18.1.tgz", - "integrity": "sha512-xcsXcMkIMd7l3WZEWoc4ljteAiqzxb5gVerRxk5132p5cLix6rTydWTQjsj2oxORepfsrwy1fC4r20iMa9plrg==", - "dependencies": { - "@codemirror/state": "^6.1.4", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "dev": true, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", - "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", - "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", - "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@fal-works/esbuild-plugin-global-externals": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", - "integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==", - "dev": true - }, - "node_modules/@floating-ui/core": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.4.1.tgz", - "integrity": "sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==", - "dependencies": { - "@floating-ui/utils": "^0.1.1" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.2.tgz", - "integrity": "sha512-6ArmenS6qJEWmwzczWyhvrXRdI/rI78poBcW0h/456+onlabit+2G+QxHx5xTOX60NBJQXjsCLFbW2CmsXpUog==", - "dependencies": { - "@floating-ui/core": "^1.4.1", - "@floating-ui/utils": "^0.1.1" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", - "dev": true, - "dependencies": { - "@floating-ui/dom": "^1.5.1" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.2.tgz", - "integrity": "sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ==" - }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", - "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", - "dependencies": { - "@formatjs/intl-localematcher": "0.5.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/intl-listformat": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.3.tgz", - "integrity": "sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ==", - "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/intl-localematcher": "0.5.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", - "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@fortawesome/fontawesome-free": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz", - "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==", - "hasInstallScript": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@goauthentik/api": { - "version": "2023.10.4-1701882394", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.4-1701882394.tgz", - "integrity": "sha512-rYfJRl4IGQN6OGdWy6f+yMuNRPgmPjkPTI+6V1otR5NwgKrvZeEzy/8Vx1tEkz3CIix8rgZCmu3SmJVsep0ggQ==" - }, - "node_modules/@hcaptcha/types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@hcaptcha/types/-/types-1.0.3.tgz", - "integrity": "sha512-1mbU6eSGawRrqeahRrOzZo/SVLI6oZ5/azuBpSyVrRRR96CnS3fOVDWfzxpngfxKD0/I9Rwu6c/3ITqD8rXeTQ==", - "dev": true - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jackfranklin/rollup-plugin-markdown": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@jackfranklin/rollup-plugin-markdown/-/rollup-plugin-markdown-0.4.0.tgz", - "integrity": "sha512-9B8F/K9mzmD6cpIx1EttIVJBsHv2I1JCPYr01yxI4ibY8419wFeQ7yOykwFYNVN7wjkb+BMsct/eXnPQjt36pg==", - "dev": true, - "dependencies": { - "@types/showdown": "^2.0.0", - "gray-matter": "^4.0.2", - "rollup-pluginutils": "^2.8.2", - "showdown": "^2.1.0" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jeysal/storybook-addon-css-user-preferences": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@jeysal/storybook-addon-css-user-preferences/-/storybook-addon-css-user-preferences-0.2.0.tgz", - "integrity": "sha512-XMavNKAEVO3BzcyU8cDeZzehPu64D9GXbs2OxZBlMWBiQ1RrySmRaPdvzh7t+0umLUrnYCcJwSxgAM3mBx1znw==", - "dev": true, - "peerDependencies": { - "@storybook/addons": "^6.4.0 || ^7", - "@storybook/api": "^6.4.0 || ^7", - "@storybook/components": "^6.4.0 || ^7", - "@storybook/core-events": "^6.4.0 || ^7", - "@storybook/theming": "^6.4.0 || ^7", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@juggle/resize-observer": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", - "dev": true - }, - "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" - }, - "node_modules/@lezer/common": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.4.tgz", - "integrity": "sha512-lZHlk8p67x4aIDtJl6UQrXSOP6oi7dQR3W/geFVrENdA1JDaAJWldnVqVjPMJupbTKbzDfFcePfKttqVidS/dg==" - }, - "node_modules/@lezer/css": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.3.tgz", - "integrity": "sha512-SjSM4pkQnQdJDVc80LYzEaMiNy9txsFbI7HsMgeVF28NdLaAdHNtQ+kB/QqDUzRBV/75NTXjJ/R5IdC8QQGxMg==", - "dependencies": { - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/highlight": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz", - "integrity": "sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/html": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.6.tgz", - "integrity": "sha512-Kk9HJARZTc0bAnMQUqbtuhFVsB4AnteR2BFUWfZV7L/x1H0aAKz6YabrfJ2gk/BEgjh9L3hg5O4y2IDZRBdzuQ==", - "dependencies": { - "@lezer/common": "^1.0.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/javascript": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.7.tgz", - "integrity": "sha512-OVWlK0YEi7HM+9JRWtRkir8qvcg0/kVYg2TAMHlVtl6DU1C9yK1waEOLBMztZsV/axRJxsqfJKhzYz+bxZme5g==", - "dependencies": { - "@lezer/highlight": "^1.1.3", - "@lezer/lr": "^1.3.0" - } - }, - "node_modules/@lezer/lr": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.10.tgz", - "integrity": "sha512-BZfVvf7Re5BIwJHlZXbJn9L8lus5EonxQghyn+ih8Wl36XMFBPTXC0KM0IdUtj9w/diPHsKlXVgL+AlX2jYJ0Q==", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/python": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.8.tgz", - "integrity": "sha512-1T/XsmeF57ijrjpC0Zmrf9YeO5mn2zC1XeSNrOnc0KB+6PgxJ5m7kWKt0CnwyS74oHQXbJxUUL+QDQJR26c1Gw==", - "dependencies": { - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/xml": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.2.tgz", - "integrity": "sha512-dlngsWceOtQBMuBPw5wtHpaxdPJ71aVntqjbpGkFtWsp4WtQmCnuTjQGocviymydN6M18fhj6UQX3oiEtSuY7w==", - "dependencies": { - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lit-labs/context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@lit-labs/context/-/context-0.4.1.tgz", - "integrity": "sha512-o+uKepgEPoYAVaPvSASoDiUWKdcf7neyhFcm9dvtiLgptKoINZD1vW7GbbH/2hPtxLxgcmVfZ9NDCXNDQeHTHQ==", - "dependencies": { - "@lit/reactive-element": "^1.5.0", - "lit": "^2.7.0" - } - }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz", - "integrity": "sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==" - }, - "node_modules/@lit-labs/task": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lit-labs/task/-/task-3.1.0.tgz", - "integrity": "sha512-zMlcUtZeHDT83IiT2+CJBSoFvWDLnPEezhOCgqjxW4DmRHlbgd7jdft97T6dw4S4RvIETfI7OOyvubCV/EzTlg==", - "dependencies": { - "@lit/task": "^1.0.0" - } - }, - "node_modules/@lit/localize": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/@lit/localize/-/localize-0.11.4.tgz", - "integrity": "sha512-RRIwIX2tAm3+DuEndoXSJrFjGrAK5cb5IXo5K6jcJ6sbgD829B8rSqHC5MaKVUmXTVLIR1bk5IZOZDf9wFereA==", - "dependencies": { - "@lit/reactive-element": "^1.4.0", - "lit": "^2.3.0" - } - }, - "node_modules/@lit/localize-tools": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@lit/localize-tools/-/localize-tools-0.7.1.tgz", - "integrity": "sha512-qqJw501aEPF1j9QQmiVC25yU1By1DKEUIFgjszIierwr5jJzfVtGTj67D8UU0hF3vA2yAaWxcl4eooM1Yr0zKQ==", - "dev": true, - "dependencies": { - "@lit/localize": "^0.12.0", - "@parse5/tools": "^0.3.0", - "@xmldom/xmldom": "^0.8.2", - "fast-glob": "^3.2.7", - "fs-extra": "^10.0.0", - "jsonschema": "^1.4.0", - "lit": "^2.0.0 || ^3.0.0", - "minimist": "^1.2.5", - "parse5": "^7.1.1", - "source-map-support": "^0.5.19", - "typescript": "~5.2.0" - }, - "bin": { - "lit-localize": "bin/lit-localize.js" - } - }, - "node_modules/@lit/localize-tools/node_modules/@lit/localize": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@lit/localize/-/localize-0.12.0.tgz", - "integrity": "sha512-DrzF1nBhwyLEXDoUuVm4bCUzB1U9IgXlV+SkR7KEx4YpbaHSUuKw69ZJ5lMHJVuvbxdh23D8VeVwqs0mGF9jeQ==", - "dev": true, - "dependencies": { - "@lit/reactive-element": "^2.0.0", - "lit": "^3.0.0" - } - }, - "node_modules/@lit/localize-tools/node_modules/@lit/reactive-element": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.0.tgz", - "integrity": "sha512-wn+2+uDcs62ROBmVAwssO4x5xue/uKD3MGGZOXL2sMxReTRIT0JXKyMXeu7gh0aJ4IJNEIG/3aOnUaQvM7BMzQ==", - "dev": true, - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.1.2-pre.0" - } - }, - "node_modules/@lit/localize-tools/node_modules/lit": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.0.0.tgz", - "integrity": "sha512-nQ0teRzU1Kdj++VdmttS2WvIen8M79wChJ6guRKIIym2M3Ansg3Adj9O6yuQh2IpjxiUXlNuS81WKlQ4iL3BmA==", - "dev": true, - "dependencies": { - "@lit/reactive-element": "^2.0.0", - "lit-element": "^4.0.0", - "lit-html": "^3.0.0" - } - }, - "node_modules/@lit/localize-tools/node_modules/lit-element": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.0.tgz", - "integrity": "sha512-N6+f7XgusURHl69DUZU6sTBGlIN+9Ixfs3ykkNDfgfTkDYGGOWwHAYBhDqVswnFGyWgQYR2KiSpu4J76Kccs/A==", - "dev": true, - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.1.2-pre.0", - "@lit/reactive-element": "^2.0.0", - "lit-html": "^3.0.0" - } - }, - "node_modules/@lit/localize-tools/node_modules/lit-html": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.0.0.tgz", - "integrity": "sha512-DNJIE8dNY0dQF2Gs0sdMNUppMQT2/CvV4OVnSdg7BXAsGqkVwsE5bqQ04POfkYH5dBIuGnJYdFz5fYYyNnOxiA==", - "dev": true, - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, - "node_modules/@lit/localize-tools/node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/@lit/localize-tools/node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@lit/reactive-element": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", - "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.0.0" - } - }, - "node_modules/@lit/task": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lit/task/-/task-1.0.0.tgz", - "integrity": "sha512-7jocGBh3yGlo3kKxQggZph2txK4X5GYNWp2FAsmV9u2spzUypwrzRzXe8I72icAb02B00+k2nlvxVcrQB6vyrw==", - "dependencies": { - "@lit/reactive-element": "^1.0.0 || ^2.0.0" - } - }, - "node_modules/@mdx-js/react": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", - "integrity": "sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==", - "dev": true, - "dependencies": { - "@types/mdx": "^2.0.0", - "@types/react": ">=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "react": ">=16" - } - }, - "node_modules/@ndelangen/get-tarball": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@ndelangen/get-tarball/-/get-tarball-3.0.9.tgz", - "integrity": "sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==", - "dev": true, - "dependencies": { - "gunzip-maybe": "^1.4.2", - "pump": "^3.0.0", - "tar-fs": "^2.1.1" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@open-wc/lit-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@open-wc/lit-helpers/-/lit-helpers-0.6.0.tgz", - "integrity": "sha512-9F0Rw18Lupp8hehF299yYozN4cFMTnHeCVNtz0k18/eUkcUUb6DCWerL/ASJ9lZ4bLA/YUPmrkdgZz/xe9cKeg==", - "peerDependencies": { - "lit": "^2.0.0" - } - }, - "node_modules/@parse5/tools": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@parse5/tools/-/tools-0.3.0.tgz", - "integrity": "sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==", - "dev": true, - "dependencies": { - "parse5": "^7.0.0" - } - }, - "node_modules/@parse5/tools/node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/@patternfly/elements": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@patternfly/elements/-/elements-2.4.0.tgz", - "integrity": "sha512-mA6I76R8aHdBKRX+IbaVuD24OnYUBsjStDwGVq7ytopA0XUKx12xTwgRZGNM7wKBBFYbNKpIxxuaanQSLZTGIQ==", - "dependencies": { - "@patternfly/icons": "^1.0.2", - "@patternfly/pfe-core": "^2.4.0", - "lit": "2.6.1", - "tslib": "^2.4.1" - } - }, - "node_modules/@patternfly/elements/node_modules/lit": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.6.1.tgz", - "integrity": "sha512-DT87LD64f8acR7uVp7kZfhLRrHkfC/N4BVzAtnw9Yg8087mbBJ//qedwdwX0kzDbxgPccWRW6mFwGbRQIxy0pw==", - "dependencies": { - "@lit/reactive-element": "^1.6.0", - "lit-element": "^3.2.0", - "lit-html": "^2.6.0" - } - }, - "node_modules/@patternfly/icons": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@patternfly/icons/-/icons-1.0.2.tgz", - "integrity": "sha512-/faOTZsKkTPxuCDcWZbunknjUhJIjjN0h+OicNiFWxTq/saLp366cLhdgNf8A46oeGR/21aQTYVXTqcQA1yvOg==" - }, - "node_modules/@patternfly/patternfly": { - "version": "4.224.5", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.224.5.tgz", - "integrity": "sha512-io0huj+LCP5FgDZJDaLv1snxktTYs8iCFz/W1VDRneYoebNHLmGfQdF7Yn8bS6PF7qmN6oJKEBlq3AjmmE8vdA==" - }, - "node_modules/@patternfly/pfe-core": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@patternfly/pfe-core/-/pfe-core-2.4.1.tgz", - "integrity": "sha512-ZqN4Zk2ysrnHp84hKvUr54xeSwtzqM3qRPpMzdd2fa58htYnPfhow4TQ8LH9vArLxPzEAB3Hrfql0VW1bk5PXw==", - "dependencies": { - "@floating-ui/dom": "^1.2.6", - "lit": "^2.7.2" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@radix-ui/number": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", - "integrity": "sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@radix-ui/primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", - "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", - "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", - "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", - "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", - "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", - "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz", - "integrity": "sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-escape-keydown": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", - "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.3.tgz", - "integrity": "sha512-upXdPfqI4islj2CslyfUBNlaJCPybbqRHAi1KER7Isel9Q2AtSJ0zRBZv8mWQiFXD2nyAJ4BhC3yXgZ6kMBSrQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", - "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz", - "integrity": "sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-rect": "1.0.1", - "@radix-ui/react-use-size": "1.0.1", - "@radix-ui/rect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.3.tgz", - "integrity": "sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", - "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.2.tgz", - "integrity": "sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/number": "1.0.1", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.4", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.3", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-popper": "1.1.2", - "@radix-ui/react-portal": "1.0.3", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1", - "@radix-ui/react-use-previous": "1.0.1", - "@radix-ui/react-visually-hidden": "1.0.3", - "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz", - "integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", - "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle-group": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz", - "integrity": "sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-toggle": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toolbar": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.0.4.tgz", - "integrity": "sha512-tBgmM/O7a07xbaEkYJWYTXkIdU/1pW4/KZORR43toC/4XWyBCURK0ei9kMUdp+gTPPKBgYLxXmRSH1EVcIDp8Q==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-separator": "1.0.3", - "@radix-ui/react-toggle-group": "1.0.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", - "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", - "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", - "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", - "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz", - "integrity": "sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", - "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/rect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", - "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz", - "integrity": "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", - "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@rollup/plugin-babel": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", - "integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@rollup/pluginutils": "^5.0.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - }, - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz", - "integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz", - "integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", - "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", - "dev": true, - "dependencies": { - "serialize-javascript": "^6.0.1", - "smob": "^1.0.0", - "terser": "^5.17.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-typescript": { - "version": "11.1.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz", - "integrity": "sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.14.0||^3.0.0||^4.0.0", - "tslib": "*", - "typescript": ">=3.7.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - }, - "tslib": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", - "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.1.tgz", - "integrity": "sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.1.tgz", - "integrity": "sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.1.tgz", - "integrity": "sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.1.tgz", - "integrity": "sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.1.tgz", - "integrity": "sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.1.tgz", - "integrity": "sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.1.tgz", - "integrity": "sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.1.tgz", - "integrity": "sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.1.tgz", - "integrity": "sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.1.tgz", - "integrity": "sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.1.tgz", - "integrity": "sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.1.tgz", - "integrity": "sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.1.tgz", - "integrity": "sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sentry-internal/feedback": { - "version": "7.88.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.88.0.tgz", - "integrity": "sha512-lbK6jgO1I0M96nZQ99mcLSZ55ebwPAP6LhEWhkmc+eAfy97VpiY+qsbmgsmOzCEPqMmEUCEcI0rEZ7fiye2v2Q==", - "dependencies": { - "@sentry/core": "7.88.0", - "@sentry/types": "7.88.0", - "@sentry/utils": "7.88.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry-internal/tracing": { - "version": "7.88.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.88.0.tgz", - "integrity": "sha512-xXQdcYhsS+ourzJHjXNjZC9zakuc97udmpgaXRjEP7FjPYclIx+YXwgFBdHM2kzAwZLFOsEce5dr46GVXUDfZw==", - "dependencies": { - "@sentry/core": "7.88.0", - "@sentry/types": "7.88.0", - "@sentry/utils": "7.88.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/browser": { - "version": "7.88.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.88.0.tgz", - "integrity": "sha512-il4x3PB99nuU/OJQw2RltgYYbo8vtnYoIgneOeEiw4m0ppK1nKkMkd3vDRipGL6E/0i7IUmQfYYy6U10J5Rx+g==", - "dependencies": { - "@sentry-internal/feedback": "7.88.0", - "@sentry-internal/tracing": "7.88.0", - "@sentry/core": "7.88.0", - "@sentry/replay": "7.88.0", - "@sentry/types": "7.88.0", - "@sentry/utils": "7.88.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/core": { - "version": "7.88.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.88.0.tgz", - "integrity": "sha512-Jzbb7dcwiCO7kI0a1w+32UzWxbEn2OcZWzp55QMEeAh6nZ/5CXhXwpuHi0tW7doPj+cJdmxMTMu9LqMVfdGkzQ==", - "dependencies": { - "@sentry/types": "7.88.0", - "@sentry/utils": "7.88.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/replay": { - "version": "7.88.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.88.0.tgz", - "integrity": "sha512-em5dPKLPG7c/HGDbpIj3aHrWbA4iMwqjevqTzn+++KNO1YslkOosCaGsb1whU3AL1T9c3aIFIhZ4u3rNo+DxcA==", - "dependencies": { - "@sentry-internal/tracing": "7.88.0", - "@sentry/core": "7.88.0", - "@sentry/types": "7.88.0", - "@sentry/utils": "7.88.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry/tracing": { - "version": "7.88.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.88.0.tgz", - "integrity": "sha512-Lj4hhLraalN4w3swXkP2do1hcaQVOuLvO6eJJbcwf10b+P4CcdlwAnqvxOOCNxA5VzG4/K2BqQ8LRU4S0Ulj9A==", - "dependencies": { - "@sentry-internal/tracing": "7.88.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/types": { - "version": "7.88.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.88.0.tgz", - "integrity": "sha512-FvwvmX1pWAZKicPj4EpKyho8Wm+C4+r5LiepbbBF8oKwSPJdD2QV1fo/LWxsrzNxWOllFIVIXF5Ed3nPYQWpTw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/utils": { - "version": "7.88.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.88.0.tgz", - "integrity": "sha512-ukminfRmdBXTzk49orwJf3Lu3hR60ZRHjE2a4IXwYhyDT6JJgJqgsq1hzGXx0AyFfyS4WhfZ6QUBy7fu3BScZQ==", - "dependencies": { - "@sentry/types": "7.88.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@storybook/addon-actions": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.5.tgz", - "integrity": "sha512-lW/m9YcaNfBZk+TZLxyzHdd563mBWpsUIveOKYjcPdl/q0FblWWZrRsFHqwLK1ldZ4AZXs8J/47G8CBr6Ew2uQ==", - "dev": true, - "dependencies": { - "@storybook/core-events": "7.6.5", - "@storybook/global": "^5.0.0", - "@types/uuid": "^9.0.1", - "dequal": "^2.0.2", - "polished": "^4.2.2", - "uuid": "^9.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-backgrounds": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-7.6.5.tgz", - "integrity": "sha512-wZZOL19vg4TTRtOTl71XKqPe5hQx3XUh9Fle0wOi91FiFrBdqusrppnyS89wPS8RQG5lXEOFEUvYcMmdCcdZfw==", - "dev": true, - "dependencies": { - "@storybook/global": "^5.0.0", - "memoizerific": "^1.11.3", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-controls": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-7.6.5.tgz", - "integrity": "sha512-EdSZ2pYf74mOXZGGJ22lrDvdvL0YKc95iWv9FFEhUFOloMy/0OZPB2ybYmd2KVCy3SeIE4Zfeiw8pDXdCUniOQ==", - "dev": true, - "dependencies": { - "@storybook/blocks": "7.6.5", - "lodash": "^4.17.21", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-docs": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-7.6.5.tgz", - "integrity": "sha512-D9tZyD41IujCHiPYdfS2bKtZRJPNwO4EydzyqODXppomluhFbY3uTEaf0H1UFnJLQxWNXZ7rr3aS0V3O6yu8pA==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.3.1", - "@mdx-js/react": "^2.1.5", - "@storybook/blocks": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/components": "7.6.5", - "@storybook/csf-plugin": "7.6.5", - "@storybook/csf-tools": "7.6.5", - "@storybook/global": "^5.0.0", - "@storybook/mdx2-csf": "^1.0.0", - "@storybook/node-logger": "7.6.5", - "@storybook/postinstall": "7.6.5", - "@storybook/preview-api": "7.6.5", - "@storybook/react-dom-shim": "7.6.5", - "@storybook/theming": "7.6.5", - "@storybook/types": "7.6.5", - "fs-extra": "^11.1.0", - "remark-external-links": "^8.0.0", - "remark-slug": "^6.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/addon-docs/node_modules/@storybook/preview-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.5.tgz", - "integrity": "sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.5", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-docs/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/addon-essentials": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-7.6.5.tgz", - "integrity": "sha512-VCLj1JAEpGoqF5iFJOo1CZFFck/tg4m/98DLdQuNuXvxT6jqaF0NI9UUQuJLIGteDCR7NKRbTFc1hV3/Ev+Ziw==", - "dev": true, - "dependencies": { - "@storybook/addon-actions": "7.6.5", - "@storybook/addon-backgrounds": "7.6.5", - "@storybook/addon-controls": "7.6.5", - "@storybook/addon-docs": "7.6.5", - "@storybook/addon-highlight": "7.6.5", - "@storybook/addon-measure": "7.6.5", - "@storybook/addon-outline": "7.6.5", - "@storybook/addon-toolbars": "7.6.5", - "@storybook/addon-viewport": "7.6.5", - "@storybook/core-common": "7.6.5", - "@storybook/manager-api": "7.6.5", - "@storybook/node-logger": "7.6.5", - "@storybook/preview-api": "7.6.5", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/addon-essentials/node_modules/@storybook/preview-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.5.tgz", - "integrity": "sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.5", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-highlight": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-7.6.5.tgz", - "integrity": "sha512-CxzmIb30F9nLPQwT0lCPYhOAwGlGF4IkgkO8hYA7VfGCGUkJZEyyN/YkP/ZCUSdCIRChDBouR3KiFFd4mDFKzg==", - "dev": true, - "dependencies": { - "@storybook/global": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-links": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-7.6.5.tgz", - "integrity": "sha512-Lx4Ng+iXt0YpIrKGr+nOZlpN9ypOoEDoP/7bZ6m7GXuVAkDm3JrRCBp7e2ZKSKcTxPdjPuO9HVKkIjtqjINlpw==", - "dev": true, - "dependencies": { - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, - "node_modules/@storybook/addon-measure": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-7.6.5.tgz", - "integrity": "sha512-tlUudVQSrA+bwI4dhO8J7nYHtYdylcBZ86ybnqMmdTthsnyc7jnaFVQwbb6bbQJpPxvEvoNds5bVGUFocuvymQ==", - "dev": true, - "dependencies": { - "@storybook/global": "^5.0.0", - "tiny-invariant": "^1.3.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-outline": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-7.6.5.tgz", - "integrity": "sha512-P7X4+Z9L/l/RZW9UvvM+iuK2SUHD22KPc+dbYOifRXDovUqhfmcKVh1CUqTDMyZrg2ZAbropehMz1eI9BlQfxg==", - "dev": true, - "dependencies": { - "@storybook/global": "^5.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-toolbars": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-7.6.5.tgz", - "integrity": "sha512-/zqWbVNE/SHc8I5Prnd2Q8U57RGEIYvHfeXjfkuLcE2Quc4Iss4x/9eU7SKu4jm+IOO2s0wlN6HcqI3XEf2XxA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addon-viewport": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-7.6.5.tgz", - "integrity": "sha512-9ghKTaduIUvQ6oShmWLuwMeTjtMR4RgKeKHrTJ7THMqvE/ydDPCYeL7ugF65ocXZSEz/QmxdK7uL686ZMKsqNA==", - "dev": true, - "dependencies": { - "memoizerific": "^1.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addons": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-7.4.2.tgz", - "integrity": "sha512-3snQVlTIKgzk6apNZc9sHr+0n4riiQkKGUkoHS8B1G90qj/9OyRnkDq55A7mJ9CxskqIjei5Q+hlko87g0jDsA==", - "dev": true, - "dependencies": { - "@storybook/manager-api": "7.4.2", - "@storybook/preview-api": "7.4.2", - "@storybook/types": "7.4.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/channels": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.2.tgz", - "integrity": "sha512-Q95KnV+fTGaAV3S875+d5LlGg+bdC3bUnki3engODDS4ViSRHJ1bnXnqxKmAaS3O/52geIyWWR766YvwHw3avw==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.4.2", - "@storybook/core-events": "7.4.2", - "@storybook/global": "^5.0.0", - "qs": "^6.10.0", - "telejson": "^7.2.0", - "tiny-invariant": "^1.3.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/client-logger": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.2.tgz", - "integrity": "sha512-LC8tYrYSJwF4DHRdNYh6y8hSvccwUIv5/WOZKJDmKx7mcEm6HsVuUu16C9jsl7iy6IqJYxgVz1va3WS6852E+A==", - "dev": true, - "dependencies": { - "@storybook/global": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/core-events": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.2.tgz", - "integrity": "sha512-WCEBw+Ew8DrccnB0hpP9TXadreoOlMnWCyuXU2XrvmK/vde009leWQIsLs1rY+L17zDVuogBms62AxrDDJmMpw==", - "dev": true, - "dependencies": { - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/manager-api": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.4.2.tgz", - "integrity": "sha512-gKPG0At9AGhF32iwjiba+ILqswc3ZFj9ZIu5HjGEmaoiOfqI6TayuHoptup0QxkI/Hx8f9mNkHCwR9COrmb69w==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.4.2", - "@storybook/client-logger": "7.4.2", - "@storybook/core-events": "7.4.2", - "@storybook/csf": "^0.1.0", - "@storybook/global": "^5.0.0", - "@storybook/router": "7.4.2", - "@storybook/theming": "7.4.2", - "@storybook/types": "7.4.2", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "semver": "^7.3.7", - "store2": "^2.14.2", - "telejson": "^7.2.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/router": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.4.2.tgz", - "integrity": "sha512-TFpMrmliklWNSrF84kGnh3WcLZciqIvaAjhxahqD+kx070KLqjxrsiny7UC6PUUYZdjLkbR9m8n3SFdXAVKgLw==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.4.2", - "memoizerific": "^1.11.3", - "qs": "^6.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/theming": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.4.2.tgz", - "integrity": "sha512-wVmxZHVCqDoZgUOXTS4HRV4UClLtCydRNOEuUZ7X08QIPSA1FVL3gEpTQJfgCsyBX/cwSSofAMUbzAGEVNo+9g==", - "dev": true, - "dependencies": { - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.4.2", - "@storybook/global": "^5.0.0", - "memoizerific": "^1.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/types": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.2.tgz", - "integrity": "sha512-OOJ2TeS3Zzc6spHbdH+JXml0q4IHuYt9axmXAv1/pkhqHjA5072pyUacmlYNQeihpQOOsKLiCQUQlvtMy9fTnQ==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.4.2", - "@types/babel__core": "^7.0.0", - "@types/express": "^4.7.0", - "file-system-cache": "2.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addons/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/addons/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/addons/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@storybook/api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/api/-/api-7.6.5.tgz", - "integrity": "sha512-o1RH47iIgG4ie4hjJP1HgsCiuTKlGW0egaAy6E6Np3bDmujy5udWEf8vnXbcaBerc5ZSrQs45kfSWugHy2a4FA==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.6.5", - "@storybook/manager-api": "7.6.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/blocks": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.5.tgz", - "integrity": "sha512-/NjuYkPks5w9lKn47KLgVC5cBkwfc+ERAp0CY0Xe//BQJkP+bcI8lE8d9Qc9IXFbOTvYEULeQrFgCkesk5BmLg==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/components": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/docs-tools": "7.6.5", - "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.6.5", - "@storybook/preview-api": "7.6.5", - "@storybook/theming": "7.6.5", - "@storybook/types": "7.6.5", - "@types/lodash": "^4.14.167", - "color-convert": "^2.0.1", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "markdown-to-jsx": "^7.1.8", - "memoizerific": "^1.11.3", - "polished": "^4.2.2", - "react-colorful": "^5.1.2", - "telejson": "^7.2.0", - "tocbot": "^4.20.1", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/blocks/node_modules/@storybook/preview-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.5.tgz", - "integrity": "sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.5", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/builder-manager": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.5.tgz", - "integrity": "sha512-FQyI+tfzMam2XKXq7k921YVafIJs9Vqvos5qx8vyRnRffo55UU8tgunwjGn0PswtbMm6sThVqE0C0ZzVr7RG8A==", - "dev": true, - "dependencies": { - "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "7.6.5", - "@storybook/manager": "7.6.5", - "@storybook/node-logger": "7.6.5", - "@types/ejs": "^3.1.1", - "@types/find-cache-dir": "^3.2.1", - "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", - "browser-assert": "^1.2.1", - "ejs": "^3.1.8", - "esbuild": "^0.18.0", - "esbuild-plugin-alias": "^0.2.1", - "express": "^4.17.3", - "find-cache-dir": "^3.0.0", - "fs-extra": "^11.1.0", - "process": "^0.11.10", - "util": "^0.12.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/builder-manager/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/builder-vite": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-7.6.5.tgz", - "integrity": "sha512-VbAYTGr92lgCWTwO2Z7NgSW3f5/K4Vr0Qxa2IlTgMCymWdDbWdIQiREcmCP0vjAGM2ftq1+vxngohVgx/r7pUw==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-common": "7.6.5", - "@storybook/csf-plugin": "7.6.5", - "@storybook/node-logger": "7.6.5", - "@storybook/preview": "7.6.5", - "@storybook/preview-api": "7.6.5", - "@storybook/types": "7.6.5", - "@types/find-cache-dir": "^3.2.1", - "browser-assert": "^1.2.1", - "es-module-lexer": "^0.9.3", - "express": "^4.17.3", - "find-cache-dir": "^3.0.0", - "fs-extra": "^11.1.0", - "magic-string": "^0.30.0", - "rollup": "^2.25.0 || ^3.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "@preact/preset-vite": "*", - "typescript": ">= 4.3.x", - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0", - "vite-plugin-glimmerx": "*" - }, - "peerDependenciesMeta": { - "@preact/preset-vite": { - "optional": true - }, - "typescript": { - "optional": true - }, - "vite-plugin-glimmerx": { - "optional": true - } - } - }, - "node_modules/@storybook/builder-vite/node_modules/@storybook/preview-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.5.tgz", - "integrity": "sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.5", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/builder-vite/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/builder-vite/node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@storybook/channels": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.5.tgz", - "integrity": "sha512-FIlNkyfQy9uHoJfAFL2/wO3ASGJELFvBzURBE2rcEF/TS7GcUiqWnBfiDxAbwSEjSOm2F0eEq3UXhaZEjpJHDw==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/global": "^5.0.0", - "qs": "^6.10.0", - "telejson": "^7.2.0", - "tiny-invariant": "^1.3.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/cli": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.5.tgz", - "integrity": "sha512-w+Y8dx5oCLQVESOVmpsQuFksr/ewARKrnSKl9kwnVMN4sMgjOgoZ3zmV66J7SKexvwyuwlOjf840pmEglGdPPg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.2", - "@babel/preset-env": "^7.23.2", - "@babel/types": "^7.23.0", - "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "7.6.5", - "@storybook/core-common": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/core-server": "7.6.5", - "@storybook/csf-tools": "7.6.5", - "@storybook/node-logger": "7.6.5", - "@storybook/telemetry": "7.6.5", - "@storybook/types": "7.6.5", - "@types/semver": "^7.3.4", - "@yarnpkg/fslib": "2.10.3", - "@yarnpkg/libzip": "2.3.0", - "chalk": "^4.1.0", - "commander": "^6.2.1", - "cross-spawn": "^7.0.3", - "detect-indent": "^6.1.0", - "envinfo": "^7.7.3", - "execa": "^5.0.0", - "express": "^4.17.3", - "find-up": "^5.0.0", - "fs-extra": "^11.1.0", - "get-npm-tarball-url": "^2.0.3", - "get-port": "^5.1.1", - "giget": "^1.0.0", - "globby": "^11.0.2", - "jscodeshift": "^0.15.1", - "leven": "^3.1.0", - "ora": "^5.4.1", - "prettier": "^2.8.0", - "prompts": "^2.4.0", - "puppeteer-core": "^2.1.1", - "read-pkg-up": "^7.0.1", - "semver": "^7.3.7", - "simple-update-notifier": "^2.0.0", - "strip-json-comments": "^3.0.1", - "tempy": "^1.0.1", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "bin": { - "getstorybook": "bin/index.js", - "sb": "bin/index.js" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@storybook/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@storybook/cli/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@storybook/cli/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/cli/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/cli/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/@storybook/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/cli/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@storybook/client-logger": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.5.tgz", - "integrity": "sha512-S5aROWgssqg7tcs9lgW5wmCAz4SxMAtioiyVj5oFecmPCbQtFVIAREYzeoxE4GfJL+plrfRkum4BzziANn8EhQ==", - "dev": true, - "dependencies": { - "@storybook/global": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/codemod": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.5.tgz", - "integrity": "sha512-K5C9ltBClZ0aSyujGt3RJFtRicrUZy8nzhHrcADUj27rrQD26jH/p+Y05jWKj9JcI8SyMg978GN5X/1aw2Y31A==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.2", - "@babel/preset-env": "^7.23.2", - "@babel/types": "^7.23.0", - "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.5", - "@storybook/node-logger": "7.6.5", - "@storybook/types": "7.6.5", - "@types/cross-spawn": "^6.0.2", - "cross-spawn": "^7.0.3", - "globby": "^11.0.2", - "jscodeshift": "^0.15.1", - "lodash": "^4.17.21", - "prettier": "^2.8.0", - "recast": "^0.23.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/codemod/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/@storybook/components": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.5.tgz", - "integrity": "sha512-w4ZucbBBZ+NKMWlJKVj2I/bMBBq7gzDp9lzc4+8QaQ3vUPXKqc1ilIPYo/7UR5oxwDVMZocmMSgl9L8lvf7+Mw==", - "dev": true, - "dependencies": { - "@radix-ui/react-select": "^1.2.2", - "@radix-ui/react-toolbar": "^1.0.4", - "@storybook/client-logger": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/theming": "7.6.5", - "@storybook/types": "7.6.5", - "memoizerific": "^1.11.3", - "use-resize-observer": "^9.1.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/core-client": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-7.6.5.tgz", - "integrity": "sha512-6FtyJcz8MSl+JYwNJZ53FM6rkT27pFHWcJPdtw/9229Ec8as9RpkNeZ/NBZjRTeDkn9Ki0VOiVAefNie9tZ/8Q==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.6.5", - "@storybook/preview-api": "7.6.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/core-client/node_modules/@storybook/preview-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.5.tgz", - "integrity": "sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.5", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/core-common": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.5.tgz", - "integrity": "sha512-z4EgzZSIVbID6Ib0jhh3jimKeaDWU8OOhoZYfn3galFmgQWowWOv1oMgipWiXfRLWw9DaLFQiCHIdLANH+VO2g==", - "dev": true, - "dependencies": { - "@storybook/core-events": "7.6.5", - "@storybook/node-logger": "7.6.5", - "@storybook/types": "7.6.5", - "@types/find-cache-dir": "^3.2.1", - "@types/node": "^18.0.0", - "@types/node-fetch": "^2.6.4", - "@types/pretty-hrtime": "^1.0.0", - "chalk": "^4.1.0", - "esbuild": "^0.18.0", - "esbuild-register": "^3.5.0", - "file-system-cache": "2.3.0", - "find-cache-dir": "^3.0.0", - "find-up": "^5.0.0", - "fs-extra": "^11.1.0", - "glob": "^10.0.0", - "handlebars": "^4.7.7", - "lazy-universal-dotenv": "^4.0.0", - "node-fetch": "^2.0.0", - "picomatch": "^2.3.0", - "pkg-dir": "^5.0.0", - "pretty-hrtime": "^1.0.3", - "resolve-from": "^5.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/core-common/node_modules/@types/node": { - "version": "18.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", - "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@storybook/core-common/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@storybook/core-common/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@storybook/core-common/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@storybook/core-common/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/core-common/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@storybook/core-common/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/core-common/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@storybook/core-common/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/core-events": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.5.tgz", - "integrity": "sha512-zk2q/qicYXAzHA4oV3GDbIql+Kd4TOHUgDE8e4jPCOPp856z2ScqEKUAbiJizs6eEJOH4nW9Db1kuzgrBVEykQ==", - "dev": true, - "dependencies": { - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/core-server": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.5.tgz", - "integrity": "sha512-BfKzK/ObTjUcPvE5/r1pogCifM/4nLRhOUYJl7XekwHkOQwn19e6H3/ku1W3jDoYXBu642Dc9X7l/ERjKTqxFg==", - "dev": true, - "dependencies": { - "@aw-web-design/x-default-browser": "1.4.126", - "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "7.6.5", - "@storybook/channels": "7.6.5", - "@storybook/core-common": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.5", - "@storybook/docs-mdx": "^0.1.0", - "@storybook/global": "^5.0.0", - "@storybook/manager": "7.6.5", - "@storybook/node-logger": "7.6.5", - "@storybook/preview-api": "7.6.5", - "@storybook/telemetry": "7.6.5", - "@storybook/types": "7.6.5", - "@types/detect-port": "^1.3.0", - "@types/node": "^18.0.0", - "@types/pretty-hrtime": "^1.0.0", - "@types/semver": "^7.3.4", - "better-opn": "^3.0.2", - "chalk": "^4.1.0", - "cli-table3": "^0.6.1", - "compression": "^1.7.4", - "detect-port": "^1.3.0", - "express": "^4.17.3", - "fs-extra": "^11.1.0", - "globby": "^11.0.2", - "ip": "^2.0.0", - "lodash": "^4.17.21", - "open": "^8.4.0", - "pretty-hrtime": "^1.0.3", - "prompts": "^2.4.0", - "read-pkg-up": "^7.0.1", - "semver": "^7.3.7", - "telejson": "^7.2.0", - "tiny-invariant": "^1.3.1", - "ts-dedent": "^2.0.0", - "util": "^0.12.4", - "util-deprecate": "^1.0.2", - "watchpack": "^2.2.0", - "ws": "^8.2.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/core-server/node_modules/@storybook/preview-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.5.tgz", - "integrity": "sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.5", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/core-server/node_modules/@types/node": { - "version": "18.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", - "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@storybook/core-server/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@storybook/core-server/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@storybook/core-server/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/core-server/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/core-server/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/core-server/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/core-server/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/core-server/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@storybook/csf": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.2.tgz", - "integrity": "sha512-ePrvE/pS1vsKR9Xr+o+YwdqNgHUyXvg+1Xjx0h9LrVx7Zq4zNe06pd63F5EvzTbCbJsHj7GHr9tkiaqm7U8WRA==", - "dev": true, - "dependencies": { - "type-fest": "^2.19.0" - } - }, - "node_modules/@storybook/csf-plugin": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-7.6.5.tgz", - "integrity": "sha512-iQ8Y/Qq1IUhHRddjDVicWJA2sM7OZA1FR97OvWUT2240WjCuQSCfy32JD8TQlYjqXgEolJeLPv3zW4qH5om4LQ==", - "dev": true, - "dependencies": { - "@storybook/csf-tools": "7.6.5", - "unplugin": "^1.3.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/csf-tools": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.5.tgz", - "integrity": "sha512-1iaCh7nt+WE7Q5UwRhLLc5flMNoAV/vBr0tvDSCKiHaO+D3dZzlZOe/U+S6wegdyN2QNcvT2xs179CcrX6Qp6w==", - "dev": true, - "dependencies": { - "@babel/generator": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", - "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.5", - "fs-extra": "^11.1.0", - "recast": "^0.23.1", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/csf-tools/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/docs-mdx": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@storybook/docs-mdx/-/docs-mdx-0.1.0.tgz", - "integrity": "sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==", - "dev": true - }, - "node_modules/@storybook/docs-tools": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-7.6.5.tgz", - "integrity": "sha512-UyHkHu5Af6jMpYsR4lZ69D32GQGeA0pLAn7jaBbQndgAjBdK1ykZcifiUC7Wz1hG7+YpuYspEGuDEddOh+X8FQ==", - "dev": true, - "dependencies": { - "@storybook/core-common": "7.6.5", - "@storybook/preview-api": "7.6.5", - "@storybook/types": "7.6.5", - "@types/doctrine": "^0.0.3", - "assert": "^2.1.0", - "doctrine": "^3.0.0", - "lodash": "^4.17.21" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/docs-tools/node_modules/@storybook/preview-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.5.tgz", - "integrity": "sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.5", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/global": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", - "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", - "dev": true - }, - "node_modules/@storybook/manager": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.5.tgz", - "integrity": "sha512-y1KLH0O1PGPyMxGMvOhppzFSO7r4ibjTve5iqsI0JZwxUjNuBKRLYbrhXdAyC2iacvxYNrHgevae1k9XdD+FQw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/manager-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-7.6.5.tgz", - "integrity": "sha512-tE3OShOcs6A3XtI3NJd6hYQOZLaP++Fn0dCtowBwYh/vS1EN/AyroVmL97tsxn1DZTyoRt0GidwbB6dvLMBOwA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/router": "7.6.5", - "@storybook/theming": "7.6.5", - "@storybook/types": "7.6.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "semver": "^7.3.7", - "store2": "^2.14.2", - "telejson": "^7.2.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/manager-api/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/manager-api/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/manager-api/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@storybook/mdx2-csf": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@storybook/mdx2-csf/-/mdx2-csf-1.1.0.tgz", - "integrity": "sha512-TXJJd5RAKakWx4BtpwvSNdgTDkKM6RkXU8GK34S/LhidQ5Pjz3wcnqb0TxEkfhK/ztbP8nKHqXFwLfa2CYkvQw==", - "dev": true - }, - "node_modules/@storybook/node-logger": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.5.tgz", - "integrity": "sha512-xKw6IH1wLkIssekdBv3bd13xYKUF1t8EwqDR8BYcN8AVjZlqJMTifssqG4bYV+G/B7J3tz4ugJ5nmtWg6RQ0Qw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/postinstall": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-7.6.5.tgz", - "integrity": "sha512-12WxfpqGKsk7GQ3KWiZSbamsYK8vtRmhOTkavZ9IQkcJ/zuVfmqK80/Mds+njJMudUPzuREuSFGWACczo17EDA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/preview": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview/-/preview-7.6.5.tgz", - "integrity": "sha512-zmLa7C7yFGTYhgGZXoecdww9rx0Z5HpNi/GDBRWoNSK+FEdE8Jj2jF5NJ2ncldtYIyegz9ku29JFMKbhMj9K5Q==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/preview-api": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.4.2.tgz", - "integrity": "sha512-ihTHRYzI/sI6bD215aYppiWF+1u38TrlsNjFYJ/Grftbti5d40g5wCwvAXK41SxJNYpk6CRtfvNKOwbEAC33gg==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.4.2", - "@storybook/client-logger": "7.4.2", - "@storybook/core-events": "7.4.2", - "@storybook/csf": "^0.1.0", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.4.2", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/preview-api/node_modules/@storybook/channels": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.2.tgz", - "integrity": "sha512-Q95KnV+fTGaAV3S875+d5LlGg+bdC3bUnki3engODDS4ViSRHJ1bnXnqxKmAaS3O/52geIyWWR766YvwHw3avw==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.4.2", - "@storybook/core-events": "7.4.2", - "@storybook/global": "^5.0.0", - "qs": "^6.10.0", - "telejson": "^7.2.0", - "tiny-invariant": "^1.3.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/preview-api/node_modules/@storybook/client-logger": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.4.2.tgz", - "integrity": "sha512-LC8tYrYSJwF4DHRdNYh6y8hSvccwUIv5/WOZKJDmKx7mcEm6HsVuUu16C9jsl7iy6IqJYxgVz1va3WS6852E+A==", - "dev": true, - "dependencies": { - "@storybook/global": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/preview-api/node_modules/@storybook/core-events": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.4.2.tgz", - "integrity": "sha512-WCEBw+Ew8DrccnB0hpP9TXadreoOlMnWCyuXU2XrvmK/vde009leWQIsLs1rY+L17zDVuogBms62AxrDDJmMpw==", - "dev": true, - "dependencies": { - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/preview-api/node_modules/@storybook/types": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.4.2.tgz", - "integrity": "sha512-OOJ2TeS3Zzc6spHbdH+JXml0q4IHuYt9axmXAv1/pkhqHjA5072pyUacmlYNQeihpQOOsKLiCQUQlvtMy9fTnQ==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.4.2", - "@types/babel__core": "^7.0.0", - "@types/express": "^4.7.0", - "file-system-cache": "2.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/react-dom-shim": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.6.5.tgz", - "integrity": "sha512-Qp3N3zENdvx20ikHmz5yI03z+mAWF8bUAwUofqXarVtZUkBNtvfTfUwgAezOAF0eClClH+ktIziIKd976tLSPw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/router": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-7.6.5.tgz", - "integrity": "sha512-QiTC86gRuoepzzmS6HNJZTwfz/n27NcqtaVEIxJi1Yvsx2/kLa9NkRhylNkfTuZ1gEry9stAlKWanMsB2aKyjQ==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.6.5", - "memoizerific": "^1.11.3", - "qs": "^6.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/telemetry": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.5.tgz", - "integrity": "sha512-FiLRh9k9LoGphqgBqPYySWdGqplihiZyDwqdo+Qs19RcQ/eiKg0W7fdA09nStcdcsHmDl/1cMfRhz9KUiMtwOw==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.6.5", - "@storybook/core-common": "7.6.5", - "@storybook/csf-tools": "7.6.5", - "chalk": "^4.1.0", - "detect-package-manager": "^2.0.1", - "fetch-retry": "^5.0.2", - "fs-extra": "^11.1.0", - "read-pkg-up": "^7.0.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/telemetry/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@storybook/telemetry/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@storybook/telemetry/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@storybook/telemetry/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/telemetry/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/theming": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.5.tgz", - "integrity": "sha512-RpcWT0YEgiobO41McVPDfQQHHFnjyr1sJnNTPJIvOUgSfURdgSj17mQVxtD5xcXcPWUdle5UhIOrCixHbL/NNw==", - "dev": true, - "dependencies": { - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.6.5", - "@storybook/global": "^5.0.0", - "memoizerific": "^1.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/types": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.5.tgz", - "integrity": "sha512-Q757v+fYZZSaEpks/zDL5YgXRozxkgKakXFc+BoQHK5q5sVhJ+0jvpLJiAQAniIIaMIkqY/G24Kd6Uo6UdKBCg==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@types/babel__core": "^7.0.0", - "@types/express": "^4.7.0", - "file-system-cache": "2.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/web-components": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-7.6.5.tgz", - "integrity": "sha512-uBL1d/qSwvbmkV/JGBdLySdmyOnZKBK+hn7+lgfQfTPV7JjOmTzuifnnSrhKlI/wupdBM/LYjlax5kGYt3oBXQ==", - "dev": true, - "dependencies": { - "@storybook/client-logger": "7.6.5", - "@storybook/core-client": "7.6.5", - "@storybook/docs-tools": "7.6.5", - "@storybook/global": "^5.0.0", - "@storybook/manager-api": "7.6.5", - "@storybook/preview-api": "7.6.5", - "@storybook/types": "7.6.5", - "tiny-invariant": "^1.3.1", - "ts-dedent": "^2.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "lit": "^2.0.0 || ^3.0.0" - } - }, - "node_modules/@storybook/web-components-vite": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-7.6.5.tgz", - "integrity": "sha512-j4lfrgPjlqAeoUdfXWMESUAxrOn+YUJekGh3saxsHxXIiYnzJBdkiJju4Hzqav4j8645bJzlZBw51e6wJJsuFw==", - "dev": true, - "dependencies": { - "@storybook/builder-vite": "7.6.5", - "@storybook/core-server": "7.6.5", - "@storybook/node-logger": "7.6.5", - "@storybook/web-components": "7.6.5", - "magic-string": "^0.30.0" - }, - "engines": { - "node": "^14.18 || >=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/web-components/node_modules/@storybook/preview-api": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.5.tgz", - "integrity": "sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==", - "dev": true, - "dependencies": { - "@storybook/channels": "7.6.5", - "@storybook/client-logger": "7.6.5", - "@storybook/core-events": "7.6.5", - "@storybook/csf": "^0.1.2", - "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.5", - "@types/qs": "^6.9.5", - "dequal": "^2.0.2", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "synchronous-promise": "^2.0.15", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@swagger-api/apidom-ast": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-0.76.2.tgz", - "integrity": "sha512-yLSeI3KtfpR7tI/misqTeasFonssj9GGhCOJfSHBuRAZkrPCJf0eU8vh3pL7YPa8lqFWcPT+z/arZoMcC9VLnQ==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-error": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2", - "unraw": "^3.0.0" - } - }, - "node_modules/@swagger-api/apidom-core": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.76.2.tgz", - "integrity": "sha512-366dJJM7DFONlO3nUQfQRMJpJzZjPpWZldbHJZCcvy+aCyrNYI3Waauas7fm29UXRliPirGrd9e/ZsnW3Jimag==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^0.76.2", - "@swagger-api/apidom-error": "^0.76.2", - "@types/ramda": "~0.29.3", - "minim": "~0.23.8", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "short-unique-id": "^5.0.2", - "stampit": "^4.3.2" - } - }, - "node_modules/@swagger-api/apidom-error": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-0.76.2.tgz", - "integrity": "sha512-QxoWL+qGzwftqXSJaYLZ1Nrdtro+U1zX5Q4OLK+Ggg8Hi6Kn1SGXcHhn4JZ9J1rwrP85XCabilL3z9mhdebqWg==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-json-pointer": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.76.2.tgz", - "integrity": "sha512-2XCgA4bn8vB1VMDbSiP+6SHUTiBxx1EVLW2pgqFolhLPMdiI/QBVmoW+jEkvTPo4d5gwj/vP5WDs5QnnC9VwEA==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-error": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-ns-api-design-systems": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-0.76.2.tgz", - "integrity": "sha512-ct83R5Pvc08jeOuGShO4N0ty7VO8f46WedTDCbzT4edMRhd9Xdr5UFxkwWDuliy4uLzl9ZayHygSxfnyZKQb8g==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-error": "^0.76.2", - "@swagger-api/apidom-ns-openapi-3-1": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2" - } - }, - "node_modules/@swagger-api/apidom-ns-asyncapi-2": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-0.76.2.tgz", - "integrity": "sha512-ffV2AhF7jTBbYl2vX0nYSDufs70CmC/kNMWHkgwR2Vq86lgadUc6S/NK/djpWY8+oAU3EYmHwTqu07hpSOUb4A==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-json-schema-draft-7": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.76.2.tgz", - "integrity": "sha512-0Y32CQE6tIt4IPsoCzWAUskZSyGkfw87IIsH5Bcm3D1qIlAhPAokQbe1212MmZoLVUvqrXDqZHXnOxxMaHZvYw==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^0.76.2", - "@swagger-api/apidom-core": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-0.76.2.tgz", - "integrity": "sha512-i6nZtj3ie6SP1LhRtBeZNJuBppWkuC/+AsVfUzXkH5pM+3B7Puklc77hHdLtmvUTpd/iRBdlfsklvBVXJYPtUA==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-error": "^0.76.2", - "@swagger-api/apidom-ns-json-schema-draft-4": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2" - } - }, - "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-0.76.2.tgz", - "integrity": "sha512-Klyfi/1XkJVUZa1nJP87HPMjklmB3IxE+TSD27aZIEi7GKASu96euan0gflZaegexUBA9hsAngk98USbdpHpgQ==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-error": "^0.76.2", - "@swagger-api/apidom-ns-json-schema-draft-6": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-3-0": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.76.2.tgz", - "integrity": "sha512-tV7dfbAZjX4HHul6JzmWsipMIVHCX5fAsBwLTltq8qmF9X9m6kZwg7fb4pD+cGK2KVlZl/ucDDDIQLDRWpOAog==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-json-schema-draft-4": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2" - } - }, - "node_modules/@swagger-api/apidom-ns-openapi-3-1": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.76.2.tgz", - "integrity": "sha512-Mb9VhVacoWvQcBqxO4j0eweyM6PGupAOt7XcOL5CzID0dOU+P4BbAv6kHD++0bTqRgXk1O31HkS/yPJmPaTCrw==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^0.76.2", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-openapi-3-0": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-0.76.2.tgz", - "integrity": "sha512-mJ4HLVIR9YHgWu0SiHykFQ9Sz1f3eV5Wqhrff8sH2Qll+4QSSdOOs0tW4Gp56F0HIcrU66uvrrTy1tpkO943aw==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-api-design-systems": "^0.76.2", - "@swagger-api/apidom-parser-adapter-json": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-0.76.2.tgz", - "integrity": "sha512-ot0F8Pw9/oWce6daDK+3srhNad/Iva/OlkVtN0S9cR58Zcn8p1F3s6RcN7ZG97i8EdBuyQj6Bm0jzXnOX+lvtQ==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-api-design-systems": "^0.76.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-0.76.2.tgz", - "integrity": "sha512-FK06pb4w5E8RQ65Nh1FHHM8aWzPL7fHr2HeuXZkbSeKu4j0xyzwYkxZVGwZJOT6YPJR0Yrkb/2rD89CNXsLctA==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-asyncapi-2": "^0.76.2", - "@swagger-api/apidom-parser-adapter-json": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-0.76.2.tgz", - "integrity": "sha512-7TGhZgHZ9nmBJnFA7YhDWbNDbKoUOGVkBqx563ExHr2FewaohiQ/wagXAhKZzOK+HS+KHvob09uROtqOWGdIew==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-asyncapi-2": "^0.76.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-json": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-0.76.2.tgz", - "integrity": "sha512-vbH7EcldZ/gSK9FnGUW1cpibM5+hiJPQcoyLmzLZe8YBxX73qzd2WAd77v+uI56eO9Z0G4KMCRCF9PDZT/tz5Q==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^0.76.2", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-error": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2", - "tree-sitter": "=0.20.4", - "tree-sitter-json": "=0.20.0", - "web-tree-sitter": "=0.20.3" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-0.76.2.tgz", - "integrity": "sha512-Kqcq5QUgz1TcCuPaL+zU+wmdAEo7YM0LR5jyWQo3FAT3BhAsmeVv2wRZMiz9RMDrPyxzHzbJhjMZxCqL8r2G0g==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-openapi-3-0": "^0.76.2", - "@swagger-api/apidom-parser-adapter-json": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-0.76.2.tgz", - "integrity": "sha512-kfZ4BBxww5afiIIeFT6l0/Kuob72dnYAP+Qnmp2zQB3GQUTilKqv+ddj4blCF19n8RGNERVv2RDHLTZhjg+1AA==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-openapi-3-1": "^0.76.2", - "@swagger-api/apidom-parser-adapter-json": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-0.76.2.tgz", - "integrity": "sha512-spXabhd0sgX87QaYUDou22KduSL5GHCmLNuPDpPykYelB/zZnE8aPsrjBMIgK9CPZoQCDoWYYmtRTPfJjKwf3Q==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-openapi-3-0": "^0.76.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-0.76.2.tgz", - "integrity": "sha512-KIEg9QWeiMMKQ9VtftK+1Rc7irKQjj0VTsoEtraun9N2MWLVt7g+xZKqbqtQ4/ovv5J8JBHE+hFGLdm2qZalsg==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-ns-openapi-3-1": "^0.76.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.0.0" - } - }, - "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-0.76.2.tgz", - "integrity": "sha512-nmEDYOfqeB8yCHbQ5yEQkJ09zIDOeX61KXTUktP4yErm96WVjIUk5YTTAkO7QbAEND9JHE+BAnS25cBC8BxFFA==", - "optional": true, - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^0.76.2", - "@swagger-api/apidom-core": "^0.76.2", - "@swagger-api/apidom-error": "^0.76.2", - "@types/ramda": "~0.29.3", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2", - "tree-sitter": "=0.20.4", - "tree-sitter-yaml": "=0.5.0", - "web-tree-sitter": "=0.20.3" - } - }, - "node_modules/@swagger-api/apidom-reference": { - "version": "0.76.2", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.76.2.tgz", - "integrity": "sha512-O1qX6Tql+B18Em/ERyqCzuhcvOG3JeRq4QIHfebzS3lNxpxX6si/z0DrL5K1azBldmnXx7UGqt/fvwq8GQJmIA==", - "dependencies": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.76.2", - "@types/ramda": "~0.29.3", - "axios": "^1.4.0", - "minimatch": "^7.4.3", - "process": "^0.11.10", - "ramda": "~0.29.0", - "ramda-adjunct": "^4.1.1", - "stampit": "^4.3.2" - }, - "optionalDependencies": { - "@swagger-api/apidom-error": "^0.76.2", - "@swagger-api/apidom-json-pointer": "^0.76.2", - "@swagger-api/apidom-ns-asyncapi-2": "^0.76.2", - "@swagger-api/apidom-ns-openapi-3-0": "^0.76.2", - "@swagger-api/apidom-ns-openapi-3-1": "^0.76.2", - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.76.2", - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.76.2", - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.76.2", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.76.2", - "@swagger-api/apidom-parser-adapter-json": "^0.76.2", - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.76.2", - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.76.2", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.76.2", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^0.76.2", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.76.2" - } - }, - "node_modules/@swagger-api/apidom-reference/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@swagger-api/apidom-reference/node_modules/minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz", - "integrity": "sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==", - "dev": true, - "dependencies": { - "@babel/generator": "7.17.7", - "@babel/parser": "^7.20.5", - "@babel/traverse": "7.23.2", - "@babel/types": "7.17.0", - "javascript-natural-sort": "0.7.1", - "lodash": "^4.17.21" - }, - "peerDependencies": { - "@vue/compiler-sfc": "3.x", - "prettier": "2.x - 3.x" - }, - "peerDependenciesMeta": { - "@vue/compiler-sfc": { - "optional": true - } - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/generator": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", - "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", - "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", - "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/chart.js": { - "version": "2.9.41", - "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.41.tgz", - "integrity": "sha512-3dvkDvueckY83UyUXtJMalYoH6faOLkWQoaTlJgB4Djde3oORmNP0Jw85HtzTuXyliUHcdp704s0mZFQKio/KQ==", - "dev": true, - "dependencies": { - "moment": "^2.10.2" - } - }, - "node_modules/@types/codemirror": { - "version": "5.60.15", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.15.tgz", - "integrity": "sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==", - "dev": true, - "dependencies": { - "@types/tern": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.36", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", - "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/d3-scale": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.4.tgz", - "integrity": "sha512-eq1ZeTj0yr72L8MQk6N6heP603ubnywSDRfNpi5enouR112HzGLS6RIvExCzZTraFF4HdzNpJMwA/zGiMoHUUw==", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==" - }, - "node_modules/@types/d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" - }, - "node_modules/@types/debug": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", - "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/detect-port": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.3.tgz", - "integrity": "sha512-bV/jQlAJ/nPY3XqSatkGpu+nGzou+uSwrH1cROhn+jBFg47yaNH+blW4C7p9KhopC7QxCv/6M86s37k8dMk0Yg==", - "dev": true - }, - "node_modules/@types/doctrine": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.3.tgz", - "integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==", - "dev": true - }, - "node_modules/@types/ejs": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", - "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", - "dev": true - }, - "node_modules/@types/emscripten": { - "version": "1.39.10", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.10.tgz", - "integrity": "sha512-TB/6hBkYQJxsZHSqyeuO1Jt0AB/bW6G7rHt9g7lML7SOF6lbgcHvw/Lr+69iqN0qxgXLhWKScAon73JNnptuDw==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.36", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", - "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/find-cache-dir": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz", - "integrity": "sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==", - "dev": true - }, - "node_modules/@types/fs-extra": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz", - "integrity": "sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/grecaptcha": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/grecaptcha/-/grecaptcha-3.0.7.tgz", - "integrity": "sha512-ah5GDQfsiK3dnkaCbYcDFZXkZCG3o90VRu9hzXHnSe4kACrRB1KUI/ZyWHvYmqm1W5Tl8B5YxxT98uGTlkbf2Q==", - "dev": true - }, - "node_modules/@types/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", - "dev": true - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true - }, - "node_modules/@types/lodash": { - "version": "4.14.198", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.198.tgz", - "integrity": "sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==", - "dev": true - }, - "node_modules/@types/mdast": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", - "integrity": "sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==", - "dependencies": { - "@types/unist": "^2" - } - }, - "node_modules/@types/mdx": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.10.tgz", - "integrity": "sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, - "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" - }, - "node_modules/@types/node": { - "version": "20.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.0.tgz", - "integrity": "sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg==", - "dev": true - }, - "node_modules/@types/node-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", - "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "node_modules/@types/pretty-hrtime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz", - "integrity": "sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==", - "dev": true - }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.8", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", - "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", - "dev": true - }, - "node_modules/@types/ramda": { - "version": "0.29.3", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.29.3.tgz", - "integrity": "sha512-Yh/RHkjN0ru6LVhSQtTkCRo6HXkfL9trot/2elzM/yXLJmbLm2v6kJc8yftTnwv1zvUob6TEtqI2cYjdqG3U0Q==", - "dependencies": { - "types-ramda": "^0.29.4" - } - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "node_modules/@types/react": { - "version": "18.2.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", - "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true - }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/showdown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.1.tgz", - "integrity": "sha512-xdnAw2nFqomkaL0QdtEk0t7yz26UkaVPl4v1pYJvtE1T0fmfQEH3JaxErEhGByEAl3zUZrkNBlneuJp0WJGqEA==", - "dev": true - }, - "node_modules/@types/tern": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", - "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", - "dev": true, - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", - "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" - }, - "node_modules/@types/unist": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz", - "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==" - }, - "node_modules/@types/uuid": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", - "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz", - "integrity": "sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/type-utils": "6.14.0", - "@typescript-eslint/utils": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz", - "integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/typescript-estree": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz", - "integrity": "sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz", - "integrity": "sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "6.14.0", - "@typescript-eslint/utils": "6.14.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.14.0.tgz", - "integrity": "sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz", - "integrity": "sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.14.0.tgz", - "integrity": "sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/typescript-estree": "6.14.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz", - "integrity": "sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.14.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@vscode/web-custom-data": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/@vscode/web-custom-data/-/web-custom-data-0.4.8.tgz", - "integrity": "sha512-rRiEeEX49wipCeGZo65mQJUEuCY3IXd6bet90eY6cMMQ9jBe2g3Njw/2ctbaxuACPnEKXTdW0dB7umxDln3Rzg==", - "dev": true - }, - "node_modules/@webcomponents/webcomponentsjs": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.8.0.tgz", - "integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w==" - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@yarnpkg/esbuild-plugin-pnp": { - "version": "3.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-3.0.0-rc.15.tgz", - "integrity": "sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==", - "dev": true, - "dependencies": { - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "esbuild": ">=0.10.0" - } - }, - "node_modules/@yarnpkg/fslib": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.10.3.tgz", - "integrity": "sha512-41H+Ga78xT9sHvWLlFOZLIhtU6mTGZ20pZ29EiZa97vnxdohJD2AF42rCoAoWfqUz486xY6fhjMH+DYEM9r14A==", - "dev": true, - "dependencies": { - "@yarnpkg/libzip": "^2.3.0", - "tslib": "^1.13.0" - }, - "engines": { - "node": ">=12 <14 || 14.2 - 14.9 || >14.10.0" - } - }, - "node_modules/@yarnpkg/fslib/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@yarnpkg/libzip": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/libzip/-/libzip-2.3.0.tgz", - "integrity": "sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==", - "dev": true, - "dependencies": { - "@types/emscripten": "^1.39.6", - "tslib": "^1.13.0" - }, - "engines": { - "node": ">=12 <14 || 14.2 - 14.9 || >14.10.0" - } - }, - "node_modules/@yarnpkg/libzip/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-node/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-styles/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/ansi-styles/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/app-root-dir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", - "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aria-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", - "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", - "dev": true, - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" - } - }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", - "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.32.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-tsconfig-paths": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/babel-plugin-tsconfig-paths/-/babel-plugin-tsconfig-paths-1.0.3.tgz", - "integrity": "sha512-eBTjzXpx0CXO2gooYPyIU1joS/eK1Vk2+oLhJDwRwIgh2+2kD/j649eYNtHjFKuXr36/4Y0ytPORLyiey7MLRA==", - "dev": true, - "peerDependencies": { - "@babel/core": "^7.9.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/better-opn": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", - "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", - "dev": true, - "dependencies": { - "open": "^8.0.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "devOptional": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.44" - }, - "engines": { - "node": ">= 5.10.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-assert": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", - "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==", - "dev": true - }, - "node_modules/browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", - "dev": true - }, - "node_modules/browserify-zlib": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", - "integrity": "sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==", - "dev": true, - "dependencies": { - "pako": "~0.2.0" - } - }, - "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001568", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", - "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chart.js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", - "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=7" - } - }, - "node_modules/chartjs-adapter-moment": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chartjs-adapter-moment/-/chartjs-adapter-moment-1.0.1.tgz", - "integrity": "sha512-Uz+nTX/GxocuqXpGylxK19YG4R3OSVf8326D+HwSTsNw1LgzyIGRo+Qujwro1wy6X+soNSnfj5t2vZ+r6EaDmA==", - "peerDependencies": { - "chart.js": ">=3.0.0", - "moment": "^2.10.2" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/codemirror": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", - "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/commands": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/search": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/construct-style-sheets-polyfill": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-3.1.0.tgz", - "integrity": "sha512-HBLKP0chz8BAY6rBdzda11c3wAZeCZ+kIG4weVC2NM3AXzxx09nhe8t0SQNdloAvg5GLuHwq/0SPOOSPvtCcKw==" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "node_modules/core-js": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz", - "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.32.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.2.tgz", - "integrity": "sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ==", - "dev": true, - "dependencies": { - "browserslist": "^4.21.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.32.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.32.2.tgz", - "integrity": "sha512-Y2rxThOuNywTjnX/PgA5vWM6CZ9QB9sz9oGeCixV8MqXZO70z/5SHzf9EeBrEBK0PN36DnEBBu9O/aGWzKuMZQ==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", - "dependencies": { - "layout-base": "^1.0.0" - } - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dev": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/country-flag-icons": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.9.tgz", - "integrity": "sha512-9jrjv2w7kRbqNtdtMdK2j3gmDIZzd5l9L2pZiQjF9J0mUcB+NKIGDNADTDHBEp8EQtjOkCOcciJGGSOpERdXPQ==" - }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" - }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true - }, - "node_modules/cytoscape": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.26.0.tgz", - "integrity": "sha512-IV+crL+KBcrCnVVUCZW+zRRRFUZQcrtdOPXki+o4CFUWLdAEYvuZLcBSJC9EBK++suamERKzeY7roq2hdovV3w==", - "dependencies": { - "heap": "^0.2.6", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", - "dependencies": { - "cose-base": "^1.0.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" - }, - "node_modules/d3": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", - "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" - }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dagre-d3-es": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", - "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", - "dependencies": { - "d3": "^7.8.2", - "lodash-es": "^4.17.21" - } - }, - "node_modules/dash-ast": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", - "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", - "dev": true - }, - "node_modules/dayjs": { - "version": "1.11.9", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", - "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", - "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "optional": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "optional": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, - "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defu": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.3.tgz", - "integrity": "sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==", - "dev": true - }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dev": true, - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delaunator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", - "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", - "dependencies": { - "robust-predicates": "^3.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "dev": true - }, - "node_modules/detect-package-manager": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", - "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", - "dev": true, - "dependencies": { - "execa": "^5.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/detect-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", - "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", - "dev": true, - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - } - }, - "node_modules/didyoumean2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/didyoumean2/-/didyoumean2-4.1.0.tgz", - "integrity": "sha512-qTBmfQoXvhKO75D/05C8m+fteQmn4U46FWYiLhXtZQInzitXLWY0EQ/2oKnpAz9g2lQWW8jYcLcT+hPJGT+kig==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.10.2", - "leven": "^3.1.0", - "lodash.deburr": "^4.1.0" - }, - "engines": { - "node": ">=10.13" - } - }, - "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dompurify": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz", - "integrity": "sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A==" - }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, - "node_modules/dotenv-expand": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", - "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/duplexify/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/duplexify/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/duplexify/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", - "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.610", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.610.tgz", - "integrity": "sha512-mqi2oL1mfeHYtOdCxbPQYV/PL7YrQlxbvFEZ0Ee8GbDdShimqt2/S6z2RWqysuvlwdOrQdqvE0KZrBTipAeJzg==", - "dev": true - }, - "node_modules/elkjs": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", - "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "devOptional": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", - "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", - "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.1", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-array-concat": "^1.0.0", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/esbuild-plugin-alias": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz", - "integrity": "sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==", - "dev": true - }, - "node_modules/esbuild-register": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.5.0.tgz", - "integrity": "sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "peerDependencies": { - "esbuild": ">=0.12 <1" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-google": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", - "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-custom-elements": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/eslint-plugin-custom-elements/-/eslint-plugin-custom-elements-0.0.8.tgz", - "integrity": "sha512-726XMAabRLKKm6/yjvYfvY4MKBwX9C4x8yPjj/ap470KhSIBHm+xHbm3P7cKlsFz/4cxq6YrBeSwKmwlacF1jg==", - "dev": true, - "peerDependencies": { - "eslint": ">=4.19.0" - } - }, - "node_modules/eslint-plugin-lit": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.11.0.tgz", - "integrity": "sha512-jVqy2juQTAtOzj1ILf+ZW5GpDobXlSw0kvpP2zu2r8ZbW7KISt7ikj1Gw9DhNeirEU1UlSJR0VIWpdr4lzjayw==", - "dev": true, - "dependencies": { - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "requireindex": "^1.2.0" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "eslint": ">= 5" - } - }, - "node_modules/eslint-plugin-sonarjs": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.23.0.tgz", - "integrity": "sha512-z44T3PBf9W7qQ/aR+NmofOTyg6HLhSEZOPD4zhStqBpLoMp8GYhFksuUBnCxbnf1nfISpKBVkQhiBLFI/F4Wlg==", - "dev": true, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-storybook": { - "version": "0.6.15", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.15.tgz", - "integrity": "sha512-lAGqVAJGob47Griu29KXYowI4G7KwMoJDOkEip8ujikuDLxU+oWJ1l0WL6F2oDO4QiyUFXvtDkEkISMOPzo+7w==", - "dev": true, - "dependencies": { - "@storybook/csf": "^0.0.1", - "@typescript-eslint/utils": "^5.45.0", - "requireindex": "^1.1.0", - "ts-dedent": "^2.2.0" - }, - "engines": { - "node": "12.x || 14.x || >= 16" - }, - "peerDependencies": { - "eslint": ">=6" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@storybook/csf": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", - "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extract-zip": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", - "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", - "dev": true, - "dependencies": { - "concat-stream": "^1.6.2", - "debug": "^2.6.9", - "mkdirp": "^0.5.4", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - } - }, - "node_modules/extract-zip/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/extract-zip/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-patch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", - "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fetch-retry": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-5.0.6.tgz", - "integrity": "sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==", - "dev": true - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-system-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-2.3.0.tgz", - "integrity": "sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==", - "dev": true, - "dependencies": { - "fs-extra": "11.1.1", - "ramda": "0.29.0" - } - }, - "node_modules/file-system-cache/node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", - "dev": true, - "dependencies": { - "flatted": "^3.2.7", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/flow-parser": { - "version": "0.224.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.224.0.tgz", - "integrity": "sha512-S1P78o0VLB1FZvkoGSIpaRiiTUQ3xDhm9I4Z1qc3lglmkjehfR2sjM0vhwKS7UC1G12VT4Leb/GGV/KlactqjA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.9.0.tgz", - "integrity": "sha512-rahaRMkN8P8d/tgK/BLPX+WBVM27NbvdXBxqQujBtkDAIFspaRqN7Od7lfdGQA6KAD+f82fYCLBq1ipvcu8qLw==" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "devOptional": true - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fuse.js": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", - "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==", - "engines": { - "node": ">=10" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-npm-tarball-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/get-npm-tarball-url/-/get-npm-tarball-url-2.1.0.tgz", - "integrity": "sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==", - "dev": true, - "engines": { - "node": ">=12.17" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/giget": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/giget/-/giget-1.1.3.tgz", - "integrity": "sha512-zHuCeqtfgqgDwvXlR84UNgnJDuUHQcNI5OqWqFxxuk2BshuKbYhJWdxBsEo4PvKqoGh23lUAIvBNpChMLv7/9Q==", - "dev": true, - "dependencies": { - "colorette": "^2.0.20", - "defu": "^6.1.2", - "https-proxy-agent": "^7.0.2", - "mri": "^1.2.0", - "node-fetch-native": "^1.4.0", - "pathe": "^1.1.1", - "tar": "^6.2.0" - }, - "bin": { - "giget": "dist/cli.mjs" - } - }, - "node_modules/giget/node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "optional": true - }, - "node_modules/github-slugger": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", - "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", - "dev": true - }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "dev": true, - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gunzip-maybe": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", - "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", - "dev": true, - "dependencies": { - "browserify-zlib": "^0.1.4", - "is-deflate": "^1.0.0", - "is-gzip": "^1.0.0", - "peek-stream": "^1.1.0", - "pumpify": "^1.3.3", - "through2": "^2.0.3" - }, - "bin": { - "gunzip-maybe": "bin.js" - } - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==" - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "optional": true - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-deflate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", - "integrity": "sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==", - "dev": true - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-gzip": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", - "integrity": "sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", - "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.11" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", - "dev": true - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jscodeshift": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.15.1.tgz", - "integrity": "sha512-hIJfxUy8Rt4HkJn/zZPU9ChKfKZM1342waJ1QC2e2YsPcWhM+3BJ4dcfQCzArTrk1jJeNLB341H+qOcEHRxJZg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.23.0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", - "@babel/plugin-transform-optional-chaining": "^7.23.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/preset-flow": "^7.22.15", - "@babel/preset-typescript": "^7.23.0", - "@babel/register": "^7.22.15", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.23.3", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - }, - "peerDependenciesMeta": { - "@babel/preset-env": { - "optional": true - } - } - }, - "node_modules/jscodeshift/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jscodeshift/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jscodeshift/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jscodeshift/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jscodeshift/node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" - }, - "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/khroma": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz", - "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/layout-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" - }, - "node_modules/lazy-universal-dotenv": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz", - "integrity": "sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==", - "dev": true, - "dependencies": { - "app-root-dir": "^1.0.2", - "dotenv": "^16.0.0", - "dotenv-expand": "^10.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/lit": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", - "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", - "dependencies": { - "@lit/reactive-element": "^1.6.0", - "lit-element": "^3.3.0", - "lit-html": "^2.8.0" - } - }, - "node_modules/lit-analyzer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lit-analyzer/-/lit-analyzer-2.0.2.tgz", - "integrity": "sha512-Is3cx8ypCVq5uNl8EKkPdlLuV3HDVntDVUeLNQlzTM2Je3uG5wHcn+06NB+yhCoa4rhwwXCjprU/7g21CSFqOA==", - "dev": true, - "dependencies": { - "@vscode/web-custom-data": "^0.4.2", - "chalk": "^2.4.2", - "didyoumean2": "4.1.0", - "fast-glob": "^3.2.11", - "parse5": "5.1.0", - "ts-simple-type": "~2.0.0-next.0", - "vscode-css-languageservice": "4.3.0", - "vscode-html-languageservice": "3.1.0", - "web-component-analyzer": "^2.0.0" - }, - "bin": { - "lit-analyzer": "cli.js" - } - }, - "node_modules/lit-analyzer/node_modules/parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", - "dev": true - }, - "node_modules/lit-element": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", - "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.1.0", - "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.8.0" - } - }, - "node_modules/lit-html": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", - "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.deburr": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", - "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-or-similar": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", - "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", - "dev": true - }, - "node_modules/markdown-to-jsx": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.3.2.tgz", - "integrity": "sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q==", - "dev": true, - "engines": { - "node": ">= 10" - }, - "peerDependencies": { - "react": ">= 0.14.0" - } - }, - "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/mdast-util-definitions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", - "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", - "dev": true, - "dependencies": { - "unist-util-visit": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", - "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "micromark": "^3.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-decode-string": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", - "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", - "dependencies": { - "@types/mdast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memoizerific": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", - "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", - "dev": true, - "dependencies": { - "map-or-similar": "^1.5.0" - } - }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "node_modules/merge-source-map": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", - "integrity": "sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==", - "dev": true, - "dependencies": { - "source-map": "^0.5.6" - } - }, - "node_modules/merge-source-map/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mermaid": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.6.1.tgz", - "integrity": "sha512-Hky0/RpOw/1il9X8AvzOEChfJtVvmXm+y7JML5C//ePYMy0/9jCEmW1E1g86x9oDfW9+iVEdTV/i+M6KWRNs4A==", - "dependencies": { - "@braintree/sanitize-url": "^6.0.1", - "@types/d3-scale": "^4.0.3", - "@types/d3-scale-chromatic": "^3.0.0", - "cytoscape": "^3.23.0", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", - "d3": "^7.4.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.10", - "dayjs": "^1.11.7", - "dompurify": "^3.0.5", - "elkjs": "^0.8.2", - "khroma": "^2.0.0", - "lodash-es": "^4.17.21", - "mdast-util-from-markdown": "^1.3.0", - "non-layered-tidy-tree-layout": "^2.0.2", - "stylis": "^4.1.3", - "ts-dedent": "^2.2.0", - "uuid": "^9.0.0", - "web-worker": "^1.2.0" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromark": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", - "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", - "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-factory-destination": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", - "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", - "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", - "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", - "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", - "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", - "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", - "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", - "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", - "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", - "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-html-tag-name": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", - "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", - "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", - "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", - "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", - "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minim": { - "version": "0.23.8", - "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", - "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", - "dependencies": { - "lodash": "^4.15.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "devOptional": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "devOptional": true - }, - "node_modules/mock-xmlhttprequest": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mock-xmlhttprequest/-/mock-xmlhttprequest-8.2.0.tgz", - "integrity": "sha512-agEokniRxw/MhrmIl9Ytn5wuj6kK+pu4KaGPV2fGEKStHXH9HIzYPChth2Jb/MB3TMX0ER1F1QMsvMA3BBe3qA==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/mutexify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", - "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", - "dev": true, - "dependencies": { - "queue-tick": "^1.0.0" - } - }, - "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "optional": true - }, - "node_modules/nanobench": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nanobench/-/nanobench-2.1.1.tgz", - "integrity": "sha512-z+Vv7zElcjN+OpzAxAquUayFLGK3JI/ubCl0Oh64YQqsTGG09CGqieJVQw4ui8huDnnAgrvTv93qi5UaOoNj8A==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^0.1.2", - "chalk": "^1.1.3", - "mutexify": "^1.1.0", - "pretty-hrtime": "^1.0.2" - }, - "bin": { - "nanobench": "run.js", - "nanobench-compare": "compare.js" - } - }, - "node_modules/nanobench/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanobench/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanobench/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanobench/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanobench/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "optional": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-abi": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", - "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", - "optional": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "optional": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, - "node_modules/node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "dev": true, - "dependencies": { - "minimatch": "^3.0.2" - }, - "engines": { - "node": ">= 0.10.5" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch-native": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.4.1.tgz", - "integrity": "sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w==", - "dev": true - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/non-layered-tidy-tree-layout": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", - "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/npm-run-all/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npm-run-all/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/oidc-client-ts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz", - "integrity": "sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==", - "dependencies": { - "crypto-js": "^4.2.0", - "jwt-decode": "^3.1.2" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ospec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ospec/-/ospec-3.1.0.tgz", - "integrity": "sha512-+nGtjV3vlADp+UGfL51miAh/hB4awPBkQrArhcgG4trAaoA2gKt5bf9w0m9ch9zOr555cHWaCHZEDiBOkNZSxw==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "ospec": "bin/ospec" - } - }, - "node_modules/ospec/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", - "dev": true, - "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", - "dev": true - }, - "node_modules/peek-stream": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", - "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "duplexify": "^3.5.0", - "through2": "^2.0.3" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dev": true, - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/polished": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", - "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.17.8" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", - "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/pseudolocale": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-2.0.0.tgz", - "integrity": "sha512-g1K9tCQYY4e3UGtnW8qs3kGWAOONxt7i5wuOFvf3N1EIIRhiLVIhZ9AM/ZyGTxsp231JbFywJU/EbJ5ZoqnZdg==", - "dev": true, - "dependencies": { - "commander": "^10.0.0" - }, - "bin": { - "pseudolocale": "dist/cli.mjs" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/pseudolocale/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "devOptional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/puppeteer-core": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-2.1.1.tgz", - "integrity": "sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==", - "dev": true, - "dependencies": { - "@types/mime-types": "^2.1.0", - "debug": "^4.1.0", - "extract-zip": "^1.6.6", - "https-proxy-agent": "^4.0.0", - "mime": "^2.0.3", - "mime-types": "^2.1.25", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^2.6.1", - "ws": "^6.1.0" - }, - "engines": { - "node": ">=8.16.0" - } - }, - "node_modules/puppeteer-core/node_modules/agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/puppeteer-core/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/puppeteer-core/node_modules/https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, - "dependencies": { - "agent-base": "5", - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/puppeteer-core/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/puppeteer-core/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/pyright": { - "version": "1.1.338", - "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.338.tgz", - "integrity": "sha512-cY4p/LZjC3E1m6If48n19vZgBOUASIOX6zMTavIo2o2JlJRd6/+gy+aYaMdmljVF2mVP8NG6OuKiGxERSdpQOw==", - "dev": true, - "bin": { - "pyright": "index.js", - "pyright-langserver": "langserver.index.js" - }, - "engines": { - "node": ">=12.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/qrjs": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/qrjs/-/qrjs-0.2.0.tgz", - "integrity": "sha512-6tOePfihDByEXDULYlT/FmV27m5rX6IehCeZ82LouBD5kzSNqNXuVog8m1KGuGNyQovVOb0nKOB2ybHRRRgKJw==" - }, - "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dev": true - }, - "node_modules/ramda": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", - "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/ramda-adjunct": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-4.1.1.tgz", - "integrity": "sha512-BnCGsZybQZMDGram9y7RiryoRHS5uwx8YeGuUeDKuZuvK38XO6JJfmK85BwRWAKFA6pZ5nZBO/HBFtExVaf31w==", - "engines": { - "node": ">=0.10.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda-adjunct" - }, - "peerDependencies": { - "ramda": ">= 0.29.0" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/rapidoc": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.3.4.tgz", - "integrity": "sha512-kqNuOSmjlf12SpSfPQaIMuehj7w8JWFFr9/l2zieG7/gCJr1NG2XL920uoqNlXzku1DO8NeHRkSXCmyaZxEOew==", - "dependencies": { - "@apitools/openapi-parser": "0.0.30", - "base64-arraybuffer": "^1.0.2", - "buffer": "^6.0.3", - "lit": "^2.6.1", - "marked": "^4.2.12", - "prismjs": "^1.29.0", - "xml-but-prettier": "^1.0.1" - }, - "engines": { - "node": ">=10.21.0" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "optional": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-colorful": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", - "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", - "dev": true, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "dev": true, - "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", - "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", - "dev": true, - "dependencies": { - "react-style-singleton": "^2.2.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dev": true, - "dependencies": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "devOptional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recast": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.4.tgz", - "integrity": "sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw==", - "dev": true, - "dependencies": { - "assert": "^2.0.0", - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, - "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/remark-external-links": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/remark-external-links/-/remark-external-links-8.0.0.tgz", - "integrity": "sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA==", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "is-absolute-url": "^3.0.0", - "mdast-util-definitions": "^4.0.0", - "space-separated-tokens": "^1.0.0", - "unist-util-visit": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-slug": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-6.1.0.tgz", - "integrity": "sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==", - "dev": true, - "dependencies": { - "github-slugger": "^1.0.0", - "mdast-util-to-string": "^1.0.0", - "unist-util-visit": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-slug/node_modules/mdast-util-to-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", - "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requireindex": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", - "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", - "dev": true, - "engines": { - "node": ">=0.10.5" - } - }, - "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" - }, - "node_modules/rollup": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.1.tgz", - "integrity": "sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.1", - "@rollup/rollup-android-arm64": "4.9.1", - "@rollup/rollup-darwin-arm64": "4.9.1", - "@rollup/rollup-darwin-x64": "4.9.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.1", - "@rollup/rollup-linux-arm64-gnu": "4.9.1", - "@rollup/rollup-linux-arm64-musl": "4.9.1", - "@rollup/rollup-linux-riscv64-gnu": "4.9.1", - "@rollup/rollup-linux-x64-gnu": "4.9.1", - "@rollup/rollup-linux-x64-musl": "4.9.1", - "@rollup/rollup-win32-arm64-msvc": "4.9.1", - "@rollup/rollup-win32-ia32-msvc": "4.9.1", - "@rollup/rollup-win32-x64-msvc": "4.9.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-copy": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz", - "integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==", - "dev": true, - "dependencies": { - "@types/fs-extra": "^8.0.1", - "colorette": "^1.1.0", - "fs-extra": "^8.1.0", - "globby": "10.0.1", - "is-plain-object": "^3.0.0" - }, - "engines": { - "node": ">=8.3" - } - }, - "node_modules/rollup-plugin-copy/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/rollup-plugin-copy/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup-plugin-copy/node_modules/globby": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", - "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", - "dev": true, - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/rollup-plugin-copy/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/rollup-plugin-copy/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/rollup-plugin-cssimport": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-cssimport/-/rollup-plugin-cssimport-1.0.3.tgz", - "integrity": "sha512-55LVDOedJJjknwvPUk2503Sbo5L5NGIBb/N6lgYwPboOBpbhhNOsT3c3ellmz86FwVjUnYyP8Cd/4GapzimI2Q==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^4.2.1" - } - }, - "node_modules/rollup-plugin-cssimport/node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", - "dev": true, - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/rollup-plugin-modify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-modify/-/rollup-plugin-modify-3.0.0.tgz", - "integrity": "sha512-p/ffs0Y2jz2dEnWjq1oVC7SY37tuS+aP7whoNaQz1EAAOPg+k3vKJo8cMMWx6xpdd0NzhX4y2YF9o/NPu5YR0Q==", - "dev": true, - "dependencies": { - "magic-string": "0.25.2", - "ospec": "3.1.0" - } - }, - "node_modules/rollup-plugin-modify/node_modules/magic-string": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", - "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.4" - } - }, - "node_modules/rollup-plugin-postcss-lit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-postcss-lit/-/rollup-plugin-postcss-lit-2.1.0.tgz", - "integrity": "sha512-rtgCG0U2GkT5aLymkZEKXLq36sgtWVJFtL97Vmek0jgaYa6FDs0Xhqd9cCHdDBXPeTctlSZGGs+tpUtIpHMFMQ==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.2", - "transform-ast": "^2.4.4" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/rollup-pluginutils/node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/short-unique-id": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.0.2.tgz", - "integrity": "sha512-4wZq1VLV4hsEx8guP5bN7XnY8UDsVXtdUDWFMP1gvEieAXolq5fWGKpuua21PRXaLn3OybTKFQNm7JGcHSWu/Q==", - "bin": { - "short-unique-id": "bin/short-unique-id", - "suid": "bin/short-unique-id" - } - }, - "node_modules/showdown": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", - "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", - "dev": true, - "dependencies": { - "commander": "^9.0.0" - }, - "bin": { - "showdown": "bin/showdown.js" - }, - "funding": { - "type": "individual", - "url": "https://www.paypal.me/tiviesantos" - } - }, - "node_modules/showdown/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smob": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.0.tgz", - "integrity": "sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true - }, - "node_modules/space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stampit": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stampit/-/stampit-4.3.2.tgz", - "integrity": "sha512-pE2org1+ZWQBnIxRPrBM2gVupkuDD0TTNIo1H6GdT/vO82NXli2z8lRE8cu/nBIHrcOCXFBAHpb9ZldrB2/qOA==" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/store2": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz", - "integrity": "sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==", - "dev": true - }, - "node_modules/storybook": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.5.tgz", - "integrity": "sha512-uHPrL+g/0v6iIVtDA8J0uWd3jDZcdr51lCR/vPXTkrCY1uVaFjswzl8EMy5PR05I7jMpKUzkJWZtFbgbh9e1Bw==", - "dev": true, - "dependencies": { - "@storybook/cli": "7.6.5" - }, - "bin": { - "sb": "index.js", - "storybook": "index.js" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/storybook-addon-mock": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/storybook-addon-mock/-/storybook-addon-mock-4.3.0.tgz", - "integrity": "sha512-N4Yepagkom0t5jk6ur3wrtGVeOYTlGx9LX6jg8iRhFbhrpfcbr+6XhmAQSE/3FiNQXwHiwdo1Az8MvlzwymcUA==", - "dev": true, - "dependencies": { - "@storybook/addons": "^7.4.2", - "@storybook/blocks": "^7.4.2", - "@storybook/channels": "^7.4.2", - "@storybook/components": "^7.4.2", - "@storybook/core-events": "^7.4.2", - "@storybook/manager-api": "^7.4.2", - "@storybook/theming": "^7.4.2", - "mock-xmlhttprequest": "^8.1.0", - "path-to-regexp": "^6.2.1", - "polished": "^4.2.2", - "prop-types": "^15.8.1", - "storybook": "^7.4.2", - "whatwg-fetch": "^3.6.19" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/storybook-addon-mock/node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "devOptional": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.padend": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.5.tgz", - "integrity": "sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-mod": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz", - "integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==" - }, - "node_modules/stylis": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", - "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/swagger-client": { - "version": "3.20.2", - "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.20.2.tgz", - "integrity": "sha512-fjOI/ut7uCeT7CHHNNIa1WIT9SnzKxyoaVnysyuarVpSodpOq6D2loZStBGU4mwpXF+KvatCw2qQk9Ub6fRzZw==", - "dependencies": { - "@babel/runtime-corejs3": "^7.22.15", - "@swagger-api/apidom-core": ">=0.76.2 <1.0.0", - "@swagger-api/apidom-json-pointer": ">=0.76.2 <1.0.0", - "@swagger-api/apidom-ns-openapi-3-1": ">=0.76.2 <1.0.0", - "@swagger-api/apidom-reference": ">=0.76.2 <1.0.0", - "cookie": "~0.5.0", - "cross-fetch": "^3.1.5", - "deepmerge": "~4.3.0", - "fast-json-patch": "^3.0.0-1", - "form-data-encoder": "^1.4.3", - "formdata-node": "^4.0.0", - "is-plain-object": "^5.0.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "qs": "^6.10.2", - "traverse": "~0.6.6", - "url": "~0.11.0" - } - }, - "node_modules/swagger-client/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/swagger-client/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/swagger-client/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/synchronous-promise": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.17.tgz", - "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", - "dev": true - }, - "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "devOptional": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "devOptional": true - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "devOptional": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/telejson": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz", - "integrity": "sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==", - "dev": true, - "dependencies": { - "memoizerific": "^1.11.3" - } - }, - "node_modules/temp": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "dev": true, - "dependencies": { - "rimraf": "~2.6.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/temp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/temp/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/tempy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", - "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", - "dev": true, - "dependencies": { - "del": "^6.0.0", - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.19.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz", - "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/tiny-invariant": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tocbot": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/tocbot/-/tocbot-4.21.1.tgz", - "integrity": "sha512-IfajhBTeg0HlMXu1f+VMbPef05QpDTsZ9X2Yn1+8npdaXsXg/+wrm9Ze1WG5OS1UDC3qJ5EQN/XOZ3gfXjPFCw==", - "dev": true - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/transform-ast": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/transform-ast/-/transform-ast-2.4.4.tgz", - "integrity": "sha512-AxjeZAcIOUO2lev2GDe3/xZ1Q0cVGjIMk5IsriTy8zbWlsEnjeB025AhkhBJHoy997mXpLd4R+kRbvnnQVuQHQ==", - "dev": true, - "dependencies": { - "acorn-node": "^1.3.0", - "convert-source-map": "^1.5.1", - "dash-ast": "^1.0.0", - "is-buffer": "^2.0.0", - "magic-string": "^0.23.2", - "merge-source-map": "1.0.4", - "nanobench": "^2.1.1" - } - }, - "node_modules/transform-ast/node_modules/magic-string": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.23.2.tgz", - "integrity": "sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.1" - } - }, - "node_modules/traverse": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", - "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tree-sitter": { - "version": "0.20.4", - "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.20.4.tgz", - "integrity": "sha512-rjfR5dc4knG3jnJNN/giJ9WOoN1zL/kZyrS0ILh+eqq8RNcIbiXA63JsMEgluug0aNvfQvK4BfCErN1vIzvKog==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "nan": "^2.17.0", - "prebuild-install": "^7.1.1" - } - }, - "node_modules/tree-sitter-json": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.20.0.tgz", - "integrity": "sha512-PteOLH+Tx6Bz4ZA/d40/DbkiSXXRM/gKahhHI8hQ1lWNfFvdknnz9k3Mz84ol5srRyLboJ8wp8GSkhZ6ht9EGQ==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "nan": "^2.14.1" - } - }, - "node_modules/tree-sitter-yaml": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/tree-sitter-yaml/-/tree-sitter-yaml-0.5.0.tgz", - "integrity": "sha512-POJ4ZNXXSWIG/W4Rjuyg36MkUD4d769YRUGKRqN+sVaj/VCo6Dh6Pkssn1Rtewd5kybx+jT1BWMyWN0CijXnMA==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "nan": "^2.14.0" - } - }, - "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "dev": true, - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "engines": { - "node": ">=6.10" - } - }, - "node_modules/ts-lit-plugin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-lit-plugin/-/ts-lit-plugin-2.0.1.tgz", - "integrity": "sha512-Y5G03aDiMYHMLzoZ50kdeVkzgVig2mBw6PVY2oI9PcWl3ONTcDyYq6rJ0QzhlACYWP8sT0dmaPMsHMObgNNvvg==", - "dev": true, - "dependencies": { - "lit-analyzer": "^2.0.1", - "web-component-analyzer": "^2.0.0" - } - }, - "node_modules/ts-simple-type": { - "version": "2.0.0-next.0", - "resolved": "https://registry.npmjs.org/ts-simple-type/-/ts-simple-type-2.0.0-next.0.tgz", - "integrity": "sha512-A+hLX83gS+yH6DtzNAhzZbPfU+D9D8lHlTSd7GeoMRBjOt3GRylDqLTYbdmjA4biWvq2xSfpqfIDj2l0OA/BVg==", - "dev": true - }, - "node_modules/ts-toolbelt": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", - "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==" - }, - "node_modules/tsconfck": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-2.1.2.tgz", - "integrity": "sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==", - "dev": true, - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^14.13.1 || ^16 || >=18" - }, - "peerDependencies": { - "typescript": "^4.3.5 || ^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "optional": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/turnstile-types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/turnstile-types/-/turnstile-types-1.2.0.tgz", - "integrity": "sha512-cTtNEtCYpcXeXR5AOD0YR0xZpXk1iZeTOuXT5vAGNowGGdgapC+k6m/lOVSkmDOUopZmPtmu311XeULIro4gUA==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, - "node_modules/types-ramda": { - "version": "0.29.4", - "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.29.4.tgz", - "integrity": "sha512-XO/820iRsCDwqLjE8XE+b57cVGPyk1h+U9lBGpDWvbEky+NQChvHVwaKM05WnW1c5z3EVQh8NhXFmh2E/1YazQ==", - "dependencies": { - "ts-toolbelt": "^9.6.0" - } - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", - "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unplugin": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.4.0.tgz", - "integrity": "sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "chokidar": "^3.5.3", - "webpack-sources": "^3.2.3", - "webpack-virtual-modules": "^0.5.0" - } - }, - "node_modules/unraw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", - "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==" - }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.2.tgz", - "integrity": "sha512-7yIgNnrST44S7PJ5+jXbdIupfU1nWUdQJBFBeJRclPXiWgCvrSq5Frw8lr/i//n5sqDfzoKmBymMS81l4U/7cg==", - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.11.2" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, - "node_modules/use-callback-ref": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", - "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", - "dev": true, - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-resize-observer": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", - "integrity": "sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==", - "dev": true, - "dependencies": { - "@juggle/resize-observer": "^3.3.1" - }, - "peerDependencies": { - "react": "16.8.0 - 18", - "react-dom": "16.8.0 - 18" - } - }, - "node_modules/use-sidecar": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", - "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", - "dev": true, - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "devOptional": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/uvu": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", - "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", - "dependencies": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3" - }, - "bin": { - "uvu": "bin.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/uvu/node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", - "dev": true, - "peer": true, - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-tsconfig-paths": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.2.2.tgz", - "integrity": "sha512-dq0FjyxHHDnp0uS3P12WEOX2W7NeuLzX9AWP38D7Zw2CTbFErapwQVlCiT5DMJcVWKQ1MMdTe92PZl/rBQ7qcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^2.1.0" - }, - "peerDependencies": { - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", - "dev": true, - "peer": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/vscode-css-languageservice": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-4.3.0.tgz", - "integrity": "sha512-BkQAMz4oVHjr0oOAz5PdeE72txlLQK7NIwzmclfr+b6fj6I8POwB+VoXvrZLTbWt9hWRgfvgiQRkh5JwrjPJ5A==", - "dev": true, - "dependencies": { - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "3.16.0-next.2", - "vscode-nls": "^4.1.2", - "vscode-uri": "^2.1.2" - } - }, - "node_modules/vscode-html-languageservice": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.1.0.tgz", - "integrity": "sha512-QAyRHI98bbEIBCqTzZVA0VblGU40na0txggongw5ZgTj9UVsVk5XbLT16O9OTcbqBGSqn0oWmFDNjK/XGIDcqg==", - "dev": true, - "dependencies": { - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "3.16.0-next.2", - "vscode-nls": "^4.1.2", - "vscode-uri": "^2.1.2" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", - "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==", - "dev": true - }, - "node_modules/vscode-languageserver-types": { - "version": "3.16.0-next.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz", - "integrity": "sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q==", - "dev": true - }, - "node_modules/vscode-nls": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.2.tgz", - "integrity": "sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==", - "dev": true - }, - "node_modules/vscode-uri": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", - "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==", - "dev": true - }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/web-component-analyzer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/web-component-analyzer/-/web-component-analyzer-2.0.0.tgz", - "integrity": "sha512-UEvwfpD+XQw99sLKiH5B1T4QwpwNyWJxp59cnlRwFfhUW6JsQpw5jMeMwi7580sNou8YL3kYoS7BWLm+yJ/jVQ==", - "dev": true, - "dependencies": { - "fast-glob": "^3.2.2", - "ts-simple-type": "2.0.0-next.0", - "typescript": "~5.2.0", - "yargs": "^17.7.2" - }, - "bin": { - "wca": "cli.js", - "web-component-analyzer": "cli.js" - } - }, - "node_modules/web-component-analyzer/node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/web-tree-sitter": { - "version": "0.20.3", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.20.3.tgz", - "integrity": "sha512-zKGJW9r23y3BcJusbgvnOH2OYAW40MXAOi9bi3Gcc7T4Gms9WWgXF8m6adsJWpGJEhgOzCrfiz1IzKowJWrtYw==", - "optional": true - }, - "node_modules/web-worker": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", - "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==" - }, - "node_modules/webcomponent-qr-code": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webcomponent-qr-code/-/webcomponent-qr-code-1.2.0.tgz", - "integrity": "sha512-1qEJ70HpiO0GbPy9T6XCtHn+RyI3awtpU88BBPQkhefwuRKg408X6z7mJeQxBnKkFQSv3ZWwmlgMvIjIO3RGOg==", - "dependencies": { - "qrjs": "^0.2.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", - "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", - "dev": true - }, - "node_modules/whatwg-fetch": { - "version": "3.6.19", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", - "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", - "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-but-prettier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", - "integrity": "sha512-C2CJaadHrZTqESlH03WOyw0oZTtoy2uEg6dSDF6YRg+9GnYNub53RRemLpnvtbHDFelxMx4LajiFsYeR6XJHgQ==", - "dependencies": { - "repeat-string": "^1.5.2" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/web/package.json b/web/package.json index 5537a6572e..87f360d9a2 100644 --- a/web/package.json +++ b/web/package.json @@ -5,134 +5,134 @@ "license": "MIT", "scripts": { "extract-locales": "lit-localize extract", - "build-locales": "run-s build-locales:build", + "build-locales": "node scripts/build-locales.mjs", "build-locales:build": "lit-localize build", "build-locales:repair": "prettier --write ./src/locale-codes.ts", - "rollup:build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' rollup -c ./rollup.config.mjs", - "rollup:build-proxy": "cross-env NODE_OPTIONS='--max_old_space_size=4096' rollup -c ./rollup.proxy.mjs", - "rollup:watch": "cross-env NODE_OPTIONS='--max_old_space_size=4096' rollup -c -w", - "build": "run-s build-locales rollup:build", - "build-proxy": "run-s build-locales rollup:build-proxy", - "watch": "run-s build-locales rollup:watch", - "lint": "eslint . --max-warnings 0 --fix", - "lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[M?][M?]' | cut -c8- | grep -E '\\.(ts|js|tsx|jsx)$') ", - "lint:spelling": "codespell -D - -D ../.github/codespell-dictionary.txt -I ../.github/codespell-words.txt -S './src/locales/**' ./src -s", + "esbuild:build": "node build.mjs", + "esbuild:build-proxy": "node build.mjs --proxy", + "esbuild:watch": "node build.mjs --watch", + "build": "run-s build-locales esbuild:build", + "build-proxy": "run-s build-locales esbuild:build-proxy", + "watch": "run-s build-locales esbuild:watch", + "lint": "cross-env NODE_OPTIONS='--max_old_space_size=65536' eslint . --max-warnings 0 --fix", + "lint:precommit": "bun scripts/eslint-precommit.mjs", + "lint:spelling": "node scripts/check-spelling.mjs", "lit-analyse": "lit-analyzer src", - "precommit": "run-s tsc lit-analyse lint:precommit lint:spelling prettier", + "precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier", "prequick": "run-s tsc:execute lit-analyse lint:precommit lint:spelling", "prettier-check": "prettier --check .", "prettier": "prettier --write .", - "pseudolocalize:build-extract-script": "cd scripts && tsc --esModuleInterop --module es2020 --moduleResolution 'node' pseudolocalize.ts && mv pseudolocalize.js pseudolocalize.mjs", - "pseudolocalize:extract": "node scripts/pseudolocalize.mjs", - "pseudolocalize": "run-s pseudolocalize:build-extract-script pseudolocalize:extract", + "pseudolocalize": "node scripts/pseudolocalize.mjs", "tsc:execute": "tsc --noEmit -p .", "tsc": "run-s build-locales tsc:execute", "storybook": "storybook dev -p 6006", - "storybook:build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' storybook build", - "storybook:build-import-map": "run-s storybook:build-import-map-script storybook:run-import-map-script", - "storybook:build-import-map-script": "cd scripts && tsc --esModuleInterop --module es2020 --target es2020 --moduleResolution 'node' build-storybook-import-maps.ts && mv build-storybook-import-maps.js build-storybook-import-maps.mjs", - "storybook:run-import-map-script": "node scripts/build-storybook-import-maps.mjs" + "storybook:build": "cross-env NODE_OPTIONS='--max_old_space_size=8192' storybook build", + "storybook:build-import-map": "node scripts/build-storybook-import-maps.mjs" }, "dependencies": { - "@codemirror/lang-html": "^6.4.7", - "@codemirror/lang-javascript": "^6.2.1", - "@codemirror/lang-python": "^6.1.3", - "@codemirror/lang-xml": "^6.0.2", - "@codemirror/legacy-modes": "^6.3.3", + "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-python": "^6.1.6", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/legacy-modes": "^6.4.0", "@codemirror/theme-one-dark": "^6.1.2", - "@formatjs/intl-listformat": "^7.5.3", - "@fortawesome/fontawesome-free": "^6.5.1", - "@goauthentik/api": "^2023.10.4-1701882394", - "@lit-labs/context": "^0.4.0", - "@lit-labs/task": "^3.1.0", - "@lit/localize": "^0.11.4", - "@open-wc/lit-helpers": "^0.6.0", - "@patternfly/elements": "^2.4.0", + "@formatjs/intl-listformat": "^7.5.7", + "@fortawesome/fontawesome-free": "^6.5.2", + "@goauthentik/api": "^2024.4.2-1718378698", + "@lit/context": "^1.1.2", + "@lit/localize": "^0.12.1", + "@lit/reactive-element": "^2.0.4", + "@lit/task": "^1.0.1", + "@open-wc/lit-helpers": "^0.7.0", + "@patternfly/elements": "^3.0.2", "@patternfly/patternfly": "^4.224.2", - "@sentry/browser": "^7.88.0", - "@sentry/tracing": "^7.88.0", + "@sentry/browser": "^8.9.2", "@webcomponents/webcomponentsjs": "^2.8.0", "base64-js": "^1.5.1", - "chart.js": "^4.4.1", + "chart.js": "^4.4.3", "chartjs-adapter-moment": "^1.0.1", "codemirror": "^6.0.1", "construct-style-sheets-polyfill": "^3.1.0", - "core-js": "^3.34.0", - "country-flag-icons": "^1.5.9", + "core-js": "^3.37.1", + "country-flag-icons": "^1.5.12", "fuse.js": "^7.0.0", - "lit": "^2.8.0", - "mermaid": "^10.6.1", + "guacamole-common-js": "^1.5.0", + "lit": "^3.1.4", + "md-front-matter": "^1.0.4", + "mermaid": "^10.9.1", "oidc-client-ts": "^2.4.0", "rapidoc": "^9.3.4", - "style-mod": "^4.1.0", + "showdown": "^2.1.0", + "style-mod": "^4.1.2", + "ts-pattern": "^5.2.0", "webcomponent-qr-code": "^1.2.0", - "yaml": "^2.3.4" + "yaml": "^2.4.5" }, "devDependencies": { - "@babel/core": "^7.23.6", + "@babel/core": "^7.24.7", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-decorators": "^7.23.6", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-runtime": "^7.23.6", - "@babel/preset-env": "^7.23.6", - "@babel/preset-typescript": "^7.23.3", + "@babel/plugin-proposal-decorators": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", "@hcaptcha/types": "^1.0.3", - "@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", - "@lit/localize-tools": "^0.7.1", - "@rollup/plugin-babel": "^6.0.4", - "@rollup/plugin-commonjs": "^25.0.7", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.5", - "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^11.1.5", - "@storybook/addon-essentials": "^7.6.5", - "@storybook/addon-links": "^7.6.5", - "@storybook/api": "^7.6.5", - "@storybook/blocks": "^7.6.4", - "@storybook/manager-api": "^7.6.5", - "@storybook/web-components": "^7.6.5", - "@storybook/web-components-vite": "^7.6.5", + "@lit/localize-tools": "^0.7.2", + "@rollup/plugin-replace": "^5.0.7", + "@spotlightjs/spotlight": "^2.0.0", + "@storybook/addon-essentials": "^8.1.10", + "@storybook/addon-links": "^8.1.10", + "@storybook/api": "^7.6.17", + "@storybook/blocks": "^8.0.8", + "@storybook/manager-api": "^8.1.10", + "@storybook/web-components": "^8.1.10", + "@storybook/web-components-vite": "^8.1.10", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/chart.js": "^2.9.41", "@types/codemirror": "5.60.15", - "@types/grecaptcha": "^3.0.7", - "@typescript-eslint/eslint-plugin": "^6.14.0", - "@typescript-eslint/parser": "^6.14.0", + "@types/grecaptcha": "^3.0.9", + "@types/guacamole-common-js": "1.5.2", + "@types/showdown": "^2.0.6", + "@typescript-eslint/eslint-plugin": "^7.5.0", + "@typescript-eslint/parser": "^7.5.0", "babel-plugin-macros": "^3.1.0", "babel-plugin-tsconfig-paths": "^1.0.3", + "chokidar": "^3.6.0", "cross-env": "^7.0.3", - "eslint": "^8.56.0", + "esbuild": "^0.21.5", + "eslint": "^8.57.0", "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-lit": "^1.11.0", - "eslint-plugin-sonarjs": "^0.23.0", - "eslint-plugin-storybook": "^0.6.15", - "lit-analyzer": "^2.0.2", + "eslint-plugin-sonarjs": "^0.25.1", + "eslint-plugin-storybook": "^0.8.0", + "github-slugger": "^2.0.0", + "glob": "^10.4.1", + "lit-analyzer": "^2.0.3", "npm-run-all": "^4.1.5", - "prettier": "^3.1.1", + "prettier": "^3.3.2", "pseudolocale": "^2.0.0", - "pyright": "=1.1.338", "react": "^18.2.0", - "react-dom": "^18.2.0", - "rollup": "^4.9.1", - "rollup-plugin-copy": "^3.5.0", - "rollup-plugin-cssimport": "^1.0.3", + "react-dom": "^18.3.1", "rollup-plugin-modify": "^3.0.0", "rollup-plugin-postcss-lit": "^2.1.0", - "storybook": "^7.6.5", - "storybook-addon-mock": "^4.3.0", - "ts-lit-plugin": "^2.0.1", - "tslib": "^2.6.2", - "turnstile-types": "^1.2.0", - "typescript": "^5.3.3", - "vite-tsconfig-paths": "^4.2.2" + "storybook": "^8.1.10", + "storybook-addon-mock": "^5.0.0", + "ts-lit-plugin": "^2.0.2", + "tslib": "^2.6.3", + "turnstile-types": "^1.2.1", + "typescript": "^5.4.5", + "vite-tsconfig-paths": "^4.3.2" }, "optionalDependencies": { - "@esbuild/darwin-arm64": "^0.19.9", + "@esbuild/darwin-arm64": "^0.21.4", "@esbuild/linux-amd64": "^0.18.11", - "@esbuild/linux-arm64": "^0.19.9" + "@esbuild/linux-arm64": "^0.21.4", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0" }, "engines": { "node": ">=20" diff --git a/web/rollup.config.mjs b/web/rollup.config.mjs deleted file mode 100644 index 49825a29dd..0000000000 --- a/web/rollup.config.mjs +++ /dev/null @@ -1,175 +0,0 @@ -import markdown from "@jackfranklin/rollup-plugin-markdown"; -import babel from "@rollup/plugin-babel"; -import commonjs from "@rollup/plugin-commonjs"; -import { nodeResolve } from "@rollup/plugin-node-resolve"; -import replace from "@rollup/plugin-replace"; -import terser from "@rollup/plugin-terser"; -import { cwd } from "process"; -import copy from "rollup-plugin-copy"; -import cssimport from "rollup-plugin-cssimport"; - -// https://github.com/d3/d3-interpolate/issues/58 -const IGNORED_WARNINGS = /Circular dependency(.*d3-[interpolate|selection])|(.*@lit\/localize.*)/; - -const extensions = [".js", ".jsx", ".ts", ".tsx"]; - -export const resources = [ - { - src: "node_modules/@patternfly/patternfly/patternfly.min.css", - dest: "dist/", - }, - { src: "src/common/styles/*", dest: "dist/" }, - { src: "src/custom.css", dest: "dist/" }, - - { - src: "node_modules/@patternfly/patternfly/assets/*", - dest: "dist/assets/", - }, - { src: "src/assets/*", dest: "dist/assets" }, - { src: "./icons/*", dest: "dist/assets/icons" }, -]; - -// eslint-disable-next-line no-undef -export const isProdBuild = process.env.NODE_ENV === "production"; -// eslint-disable-next-line no-undef -export const apiBasePath = process.env.AK_API_BASE_PATH || ""; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function manualChunks(id) { - if (id.endsWith(".md")) { - return "docs"; - } - if (id.includes("@goauthentik/api")) { - return "api"; - } - if (id.includes("locales")) { - const parts = id.split("/"); - const file = parts[parts.length - 1]; - return "locale-" + file.replace(".ts", ""); - } - if (id.includes("node_modules")) { - if (id.includes("codemirror")) { - return "vendor-cm"; - } - return "vendor"; - } -} - -export const defaultOptions = { - plugins: [ - cssimport(), - markdown(), - nodeResolve({ extensions, browser: true }), - commonjs(), - babel({ - extensions, - babelHelpers: "runtime", - include: ["src/**/*"], - }), - replace({ - "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"), - "process.env.CWD": JSON.stringify(cwd()), - "process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath), - "preventAssignment": true, - }), - isProdBuild && terser(), - ].filter((p) => p), - watch: { - clearScreen: false, - }, - preserveEntrySignatures: "strict", - cache: true, - context: "window", - onwarn: function (warning, warn) { - if (IGNORED_WARNINGS.test(warning)) { - return; - } - if (warning.code === "UNRESOLVED_IMPORT") { - throw Object.assign(new Error(), warning); - } - warn(warning); - }, -}; - -// Polyfills (imported first) -export const POLY = { - input: "./src/polyfill/poly.ts", - output: [ - { - format: "iife", - file: "dist/poly.js", - sourcemap: true, - }, - ], - cache: true, - plugins: [ - cssimport(), - nodeResolve({ browser: true }), - commonjs(), - isProdBuild && terser(), - copy({ - targets: [...resources], - copyOnce: false, - }), - ].filter((p) => p), -}; - -export const standalone = ["api-browser", "loading"].map((input) => { - return { - input: `./src/standalone/${input}`, - output: [ - { - format: "es", - dir: `dist/standalone/${input}`, - sourcemap: true, - manualChunks: manualChunks, - }, - ], - ...defaultOptions, - }; -}); - -export default [ - POLY, - // Standalone - ...standalone, - // Flow interface - { - input: "./src/flow/FlowInterface.ts", - output: [ - { - format: "es", - dir: "dist/flow", - sourcemap: true, - manualChunks: manualChunks, - }, - ], - ...defaultOptions, - }, - // Admin interface - { - input: "./src/admin/AdminInterface/AdminInterface.ts", - output: [ - { - format: "es", - dir: "dist/admin", - sourcemap: true, - manualChunks: manualChunks, - }, - ], - ...defaultOptions, - }, - // User interface - { - input: "./src/user/UserInterface.ts", - output: [ - { - format: "es", - dir: "dist/user", - sourcemap: true, - manualChunks: manualChunks, - }, - ], - ...defaultOptions, - }, -]; diff --git a/web/rollup.proxy.mjs b/web/rollup.proxy.mjs deleted file mode 100644 index feef356040..0000000000 --- a/web/rollup.proxy.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { POLY, standalone } from "./rollup.config.mjs"; - -export default [POLY, ...standalone]; diff --git a/web/scripts/build-locales.mjs b/web/scripts/build-locales.mjs new file mode 100644 index 0000000000..de8febbefa --- /dev/null +++ b/web/scripts/build-locales.mjs @@ -0,0 +1,67 @@ +import { spawnSync } from "child_process"; +import fs from "fs"; +import path from "path"; +import process from "process"; + +const localizeRules = JSON.parse(fs.readFileSync("./lit-localize.json", "utf-8")); + +function compareXlfAndSrc(loc) { + const xlf = path.join("./xliff", `${loc}.xlf`); + const src = path.join("./src/locales", `${loc}.ts`); + + // Returns false if: the expected XLF file doesn't exist, The expected + // generated file doesn't exist, or the XLF file is newer (has a higher date) + // than the generated file. The missing XLF file is important enough it + // generates a unique error message and halts the build. + + try { + var xlfStat = fs.statSync(xlf); + } catch (_error) { + console.error(`lit-localize expected '${loc}.xlf', but XLF file is not present`); + process.exit(1); + } + + try { + var srcStat = fs.statSync(src); + } catch (_error) { + return false; + } + + // if the xlf is newer (greater) than src, it's out of date. + if (xlfStat.mtimeMs > srcStat.mtimeMs) { + return false; + } + return true; +} + +// For all the expected files, find out if any aren't up-to-date. + +const upToDate = localizeRules.targetLocales.reduce( + (acc, loc) => acc && compareXlfAndSrc(loc), + true, +); + +if (!upToDate) { + const status = spawnSync("npm", ["run", "build-locales:build"], { encoding: "utf8" }); + + // Count all the missing message warnings + const counts = status.stderr.split("\n").reduce((acc, line) => { + const match = /^([\w-]+) message/.exec(line); + if (!match) { + return acc; + } + acc.set(match[1], (acc.get(match[1]) || 0) + 1); + return acc; + }, new Map()); + + const locales = Array.from(counts.keys()); + locales.sort(); + + const report = locales + .map((locale) => `Locale '${locale}' has ${counts.get(locale)} missing translations`) + .join("\n"); + + console.log(`Translation tables rebuilt.\n${report}\n`); +} + +console.log("Locale ./src is up-to-date"); diff --git a/web/scripts/build-storybook-import-maps.ts b/web/scripts/build-storybook-import-maps.mjs similarity index 77% rename from web/scripts/build-storybook-import-maps.ts rename to web/scripts/build-storybook-import-maps.mjs index 121ad105fb..dbf94c200a 100644 --- a/web/scripts/build-storybook-import-maps.ts +++ b/web/scripts/build-storybook-import-maps.mjs @@ -5,13 +5,12 @@ import { fileURLToPath } from "url"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); // eslint-disable-next-line @typescript-eslint/no-explicit-any -function* walkFilesystem(dir: string): Generator { +function* walkFilesystem(dir) { const openeddir = fs.opendirSync(dir); if (!openeddir) { return; } - - let d: fs.Dirent | null; + let d; while ((d = openeddir?.readSync())) { if (!d) { break; @@ -24,13 +23,14 @@ function* walkFilesystem(dir: string): Generator { } const import_re = /^(import \w+ from .*\.css)";/; -function extractImportLinesFromFile(path: string) { + +function extractImportLinesFromFile(path) { const source = fs.readFileSync(path, { encoding: "utf8", flag: "r" }); const lines = source?.split("\n") ?? []; return lines.filter((l) => import_re.test(l)); } -function createOneImportLine(line: string) { +function createOneImportLine(line) { const importMatch = import_re.exec(line); if (!importMatch) { throw new Error("How did an unmatchable line get here?"); @@ -39,19 +39,20 @@ function createOneImportLine(line: string) { if (!importContent) { throw new Error("How did an unmatchable line get here!?"); } - return `'${importContent}";': '${importContent}?inline";',`; + return ` '${importContent}";',`; } const isSourceFile = /\.ts$/; + function getTheSourceFiles() { return Array.from(walkFilesystem(path.join(__dirname, "..", "src"))).filter((path) => isSourceFile.test(path), ); } -function getTheImportLines(importPaths: string[]) { - const importLines: string[] = importPaths.reduce( - (acc: string[], path) => [...acc, extractImportLinesFromFile(path)].flat(), +function getTheImportLines(importPaths) { + const importLines = importPaths.reduce( + (acc, path) => [...acc, extractImportLinesFromFile(path)].flat(), [], ); const uniqueImportLines = new Set(importLines); @@ -63,8 +64,7 @@ function getTheImportLines(importPaths: string[]) { const importPaths = getTheSourceFiles(); const importLines = getTheImportLines(importPaths); -const outputFile = ` -// THIS IS A GENERATED FILE. DO NOT EDIT BY HAND. +const outputFile = `// THIS IS A GENERATED FILE. DO NOT EDIT BY HAND. // // This file is generated by the build-storybook-import-maps script in the UI's base directory. // This is a *hack* to work around an inconsistency in the way rollup, vite, and storybook @@ -73,9 +73,17 @@ const outputFile = ` // Sometime around 2030 or so, the Javascript community may finally get its collective act together // and we'll have one unified way of doing this. I can only hope. -export const cssImportMaps = { +const rawCssImportMaps = [ ${importLines.map(createOneImportLine).join("\n")} -}; +]; + +const cssImportMaps = rawCssImportMaps.reduce( + (acc, line) => ({ ...acc, [line]: line.replace(/\\.css/, ".css?inline") }), + {}, +); + +export { cssImportMaps }; +export default cssImportMaps; `; fs.writeFileSync(path.join(__dirname, "..", ".storybook", "css-import-maps.ts"), outputFile, { diff --git a/web/scripts/check-spelling.mjs b/web/scripts/check-spelling.mjs new file mode 100644 index 0000000000..0633515956 --- /dev/null +++ b/web/scripts/check-spelling.mjs @@ -0,0 +1,15 @@ +import { execSync } from "child_process"; +import path from "path"; + +const projectRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).replace( + "\n", + "", +); +const cmd = [ + "codespell -D -", + `-D ${path.join(projectRoot, ".github/codespell-dictionary.txt")}`, + `-I ${path.join(projectRoot, ".github/codespell-words.txt")}`, + "-S './src/locales/**' ./src -s", +].join(" "); + +console.log(execSync(cmd, { encoding: "utf8" })); diff --git a/web/scripts/eslint-precommit.mjs b/web/scripts/eslint-precommit.mjs new file mode 100644 index 0000000000..c65f953e21 --- /dev/null +++ b/web/scripts/eslint-precommit.mjs @@ -0,0 +1,76 @@ +import { execFileSync } from "child_process"; +import { ESLint } from "eslint"; +import path from "path"; +import process from "process"; + +// Code assumes this script is in the './web/scripts' folder. +const projectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], { + encoding: "utf8", +}).replace("\n", ""); +process.chdir(path.join(projectRoot, "./web")); + +const eslintConfig = { + overrideConfig: { + env: { + browser: true, + es2021: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:lit/recommended", + "plugin:custom-elements/recommended", + "plugin:storybook/recommended", + "plugin:sonarjs/recommended", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 12, + sourceType: "module", + }, + plugins: ["@typescript-eslint", "lit", "custom-elements", "sonarjs"], + rules: { + "indent": "off", + "linebreak-style": ["error", "unix"], + "quotes": ["error", "double", { avoidEscape: true }], + "semi": ["error", "always"], + "@typescript-eslint/ban-ts-comment": "off", + "sonarjs/cognitive-complexity": ["error", 9], + "sonarjs/no-duplicate-string": "off", + "sonarjs/no-nested-template-literals": "off", + }, + }, +}; + +const porcelainV1 = /^(..)\s+(.*$)/; +const gitStatus = execFileSync("git", ["status", "--porcelain", "."], { encoding: "utf8" }); + +const statuses = gitStatus.split("\n").reduce((acc, line) => { + const match = porcelainV1.exec(line.replace("\n")); + if (!match) { + return acc; + } + const [status, path] = Array.from(match).slice(1, 3); + return [...acc, [status, path.split("\x00")[0]]]; +}, []); + +const isModified = /^(M|\?|\s)(M|\?|\s)/; +const modified = (s) => isModified.test(s); + +const isCheckable = /\.(ts|js|mjs)$/; +const checkable = (s) => isCheckable.test(s); + +const updated = statuses.reduce( + (acc, [status, filename]) => + modified(status) && checkable(filename) ? [...acc, path.join(projectRoot, filename)] : acc, + [], +); + +const eslint = new ESLint(eslintConfig); +const results = await eslint.lintFiles(updated); +const formatter = await eslint.loadFormatter("stylish"); +const resultText = formatter.format(results); +const errors = results.reduce((acc, result) => acc + result.errorCount, 0); + +console.log(resultText); +process.exit(errors > 1 ? 1 : 0); diff --git a/web/scripts/pseudolocalize.ts b/web/scripts/pseudolocalize.mjs similarity index 62% rename from web/scripts/pseudolocalize.ts rename to web/scripts/pseudolocalize.mjs index 308632ff94..d0495f15c8 100644 --- a/web/scripts/pseudolocalize.ts +++ b/web/scripts/pseudolocalize.mjs @@ -4,16 +4,12 @@ import pseudolocale from "pseudolocale"; import { fileURLToPath } from "url"; import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js"; -import type { Message, ProgramMessage } from "@lit/localize-tools/lib/messages.d.ts"; import { sortProgramMessages } from "@lit/localize-tools/lib/messages.js"; import { TransformLitLocalizer } from "@lit/localize-tools/lib/modes/transform.js"; -import type { Config } from "@lit/localize-tools/lib/types/config.d.ts"; -import type { Locale } from "@lit/localize-tools/lib/types/locale.d.ts"; -import type { TransformOutputConfig } from "@lit/localize-tools/lib/types/modes.d.ts"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); -const pseudoLocale: Locale = "pseudo-LOCALE" as Locale; -const targetLocales: Locale[] = [pseudoLocale]; +const pseudoLocale = "pseudo-LOCALE"; +const targetLocales = [pseudoLocale]; const baseConfig = JSON.parse(readFileSync(path.join(__dirname, "../lit-localize.json"), "utf-8")); // Need to make some internal specifications to satisfy the transformer. It doesn't actually matter @@ -21,27 +17,28 @@ const baseConfig = JSON.parse(readFileSync(path.join(__dirname, "../lit-localize // is in their common parent class, but I had to pick one. Everything else here is just pure // exploitation of the lit/localize-tools internals. -const config: Config = { +const config = { ...baseConfig, baseDir: path.join(__dirname, ".."), targetLocales, output: { - ...baseConfig, + ...baseConfig.output, mode: "transform", }, - resolve: (path: string) => path, -} as Config; + resolve: (path) => path, +}; -const pseudoMessagify = (message: ProgramMessage) => ({ +const pseudoMessagify = (message) => ({ name: message.name, contents: message.contents.map((content) => typeof content === "string" ? pseudolocale(content, { prepend: "", append: "" }) : content, ), }); -const localizer = new TransformLitLocalizer(config as Config & { output: TransformOutputConfig }); -const { messages } = localizer.extractSourceMessages(); +const localizer = new TransformLitLocalizer(config); +const messages = localizer.extractSourceMessages().messages; const translations = messages.map(pseudoMessagify); const sorted = sortProgramMessages([...messages]); const formatter = makeFormatter(config); -formatter.writeOutput(sorted, new Map([[pseudoLocale, translations]])); + +formatter.writeOutput(sorted, new Map([[pseudoLocale, translations]])); diff --git a/web/src/admin/AdminInterface/AdminInterface.ts b/web/src/admin/AdminInterface/AdminInterface.ts index 1afac9d0c3..9a510bc1bf 100644 --- a/web/src/admin/AdminInterface/AdminInterface.ts +++ b/web/src/admin/AdminInterface/AdminInterface.ts @@ -1,6 +1,4 @@ import { ROUTES } from "@goauthentik/admin/Routes"; -import { OAuthInterface } from "@goauthentik/app/common/oauth/interface"; -import { adminSettings } from "@goauthentik/app/common/oauth/settings"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_API_DRAWER_TOGGLE, @@ -9,6 +7,9 @@ import { import { configureSentry } from "@goauthentik/common/sentry"; import { me } from "@goauthentik/common/users"; import { WebsocketClient } from "@goauthentik/common/ws"; +import { OAuthLoginController } from "@goauthentik/components/oauth/controller"; +import { adminSettings } from "@goauthentik/components/oauth/settings"; +import { EnterpriseAwareInterface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/enterprise/EnterpriseStatusBanner"; import "@goauthentik/elements/messages/MessageContainer"; @@ -19,7 +20,6 @@ import { getURLParam, updateURLParams } from "@goauthentik/elements/router/Route import "@goauthentik/elements/router/RouterOutlet"; import "@goauthentik/elements/sidebar/Sidebar"; import "@goauthentik/elements/sidebar/SidebarItem"; -import { UserManagerSettings } from "oidc-client-ts"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; @@ -35,7 +35,7 @@ import { AdminApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api"; import "./AdminSidebar"; @customElement("ak-interface-admin") -export class AdminInterface extends OAuthInterface { +export class AdminInterface extends EnterpriseAwareInterface { @property({ type: Boolean }) notificationDrawerOpen = getURLParam("notificationDrawerOpen", false); @@ -50,9 +50,7 @@ export class AdminInterface extends OAuthInterface { @state() user?: SessionUser; - get oauthSettings(): UserManagerSettings { - return adminSettings; - } + oauthController: OAuthLoginController; static get styles(): CSSResult[] { return [ @@ -84,6 +82,7 @@ export class AdminInterface extends OAuthInterface { constructor() { super(); this.ws = new WebsocketClient(); + this.oauthController = new OAuthLoginController(this, adminSettings); window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => { this.notificationDrawerOpen = !this.notificationDrawerOpen; updateURLParams({ diff --git a/web/src/admin/AdminInterface/AdminSidebar.ts b/web/src/admin/AdminInterface/AdminSidebar.ts index 2f973ca7e2..b4c0d7edc9 100644 --- a/web/src/admin/AdminInterface/AdminSidebar.ts +++ b/web/src/admin/AdminInterface/AdminSidebar.ts @@ -1,23 +1,25 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_SIDEBAR_TOGGLE, VERSION } from "@goauthentik/common/constants"; import { me } from "@goauthentik/common/users"; -import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; import { AKElement } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle"; import { spread } from "@open-wc/lit-helpers"; -import { consume } from "@lit-labs/context"; import { msg, str } from "@lit/localize"; import { TemplateResult, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { map } from "lit/directives/map.js"; -import { AdminApi, CapabilitiesEnum, CoreApi, UiThemeEnum, Version } from "@goauthentik/api"; -import type { Config, SessionUser, UserSelf } from "@goauthentik/api"; +import { AdminApi, CoreApi, UiThemeEnum, Version } from "@goauthentik/api"; +import type { SessionUser, UserSelf } from "@goauthentik/api"; @customElement("ak-admin-sidebar") -export class AkAdminSidebar extends AKElement { +export class AkAdminSidebar extends WithCapabilitiesConfig(AKElement) { @property({ type: Boolean, reflect: true }) open = true; @@ -27,9 +29,6 @@ export class AkAdminSidebar extends AKElement { @state() impersonation: UserSelf["username"] | null = null; - @consume({ context: authentikConfigContext }) - public config!: Config; - constructor() { super(); new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then((version) => { @@ -123,7 +122,7 @@ export class AkAdminSidebar extends AKElement { ["/events/log", msg("Logs"), [`^/events/log/(?${UUID_REGEX})$`]], ["/events/rules", msg("Notification Rules")], ["/events/transports", msg("Notification Transports")]]], - [null, msg("Customisation"), null, [ + [null, msg("Customization"), null, [ ["/policy/policies", msg("Policies")], ["/core/property-mappings", msg("Property Mappings")], ["/blueprints/instances", msg("Blueprints")], @@ -140,9 +139,10 @@ export class AkAdminSidebar extends AKElement { ["/core/tokens", msg("Tokens and App passwords")], ["/flow/stages/invitations", msg("Invitations")]]], [null, msg("System"), null, [ - ["/core/tenants", msg("Tenants")], + ["/core/brands", msg("Brands")], ["/crypto/certificates", msg("Certificates")], - ["/outpost/integrations", msg("Outpost Integrations")]]] + ["/outpost/integrations", msg("Outpost Integrations")], + ["/admin/settings", msg("Settings")]]], ]; // Typescript requires the type here to correctly type the recursive path @@ -166,7 +166,7 @@ export class AkAdminSidebar extends AKElement { ${this.renderNewVersionMessage()} ${this.renderImpersonationMessage()} ${map(sidebarContent, renderOneSidebarItem)} - ${this.renderEnterpriseMessage()} + ${this.renderEnterpriseMenu()} `; } @@ -199,8 +199,8 @@ export class AkAdminSidebar extends AKElement { : nothing; } - renderEnterpriseMessage() { - return this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise) + renderEnterpriseMenu() { + return this.can(CapabilitiesEnum.IsEnterprise) ? html` ${msg("Enterprise")} diff --git a/web/src/admin/Routes.ts b/web/src/admin/Routes.ts index 0119faaf05..af5eec0263 100644 --- a/web/src/admin/Routes.ts +++ b/web/src/admin/Routes.ts @@ -1,7 +1,7 @@ import "@goauthentik/admin/admin-overview/AdminOverviewPage"; -import "@goauthentik/app/common/oauth/callback"; -import { adminSettings } from "@goauthentik/app/common/oauth/settings"; -import "@goauthentik/app/common/oauth/signout"; +import "@goauthentik/components/oauth/callback"; +import { adminSettings } from "@goauthentik/components/oauth/settings"; +import "@goauthentik/components/oauth/signout"; import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; import { html } from "lit"; @@ -64,9 +64,9 @@ export const ROUTES: Route[] = [ await import("@goauthentik/admin/tokens/TokenListPage"); return html``; }), - new Route(new RegExp("^/core/tenants$"), async () => { - await import("@goauthentik/admin/tenants/TenantListPage"); - return html``; + new Route(new RegExp("^/core/brands"), async () => { + await import("@goauthentik/admin/brands/BrandListPage"); + return html``; }), new Route(new RegExp("^/policy/policies$"), async () => { await import("@goauthentik/admin/policies/PolicyListPage"); @@ -148,6 +148,10 @@ export const ROUTES: Route[] = [ await import("@goauthentik/admin/crypto/CertificateKeyPairListPage"); return html``; }), + new Route(new RegExp("^/admin/settings$"), async () => { + await import("@goauthentik/admin/admin-settings/AdminSettingsPage"); + return html``; + }), new Route(new RegExp("^/blueprints/instances$"), async () => { await import("@goauthentik/admin/blueprints/BlueprintListPage"); return html``; diff --git a/web/src/admin/admin-overview/AdminOverviewPage.ts b/web/src/admin/admin-overview/AdminOverviewPage.ts index a7e210a7ce..f361d11ac1 100644 --- a/web/src/admin/admin-overview/AdminOverviewPage.ts +++ b/web/src/admin/admin-overview/AdminOverviewPage.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/admin-overview/TopApplicationsTable"; import "@goauthentik/admin/admin-overview/cards/AdminStatusCard"; +import "@goauthentik/admin/admin-overview/cards/FipsStatusCard"; import "@goauthentik/admin/admin-overview/cards/RecentEventsCard"; import "@goauthentik/admin/admin-overview/cards/SystemStatusCard"; import "@goauthentik/admin/admin-overview/cards/VersionStatusCard"; @@ -10,15 +11,20 @@ import "@goauthentik/admin/admin-overview/charts/SyncStatusChart"; import { VERSION } from "@goauthentik/common/constants"; import { me } from "@goauthentik/common/users"; import { AKElement } from "@goauthentik/elements/Base"; +import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js"; import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/cards/AggregatePromiseCard"; import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html } from "lit"; +import { CSSResult, TemplateResult, css, html, nothing } from "lit"; import { customElement, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import { map } from "lit/directives/map.js"; +import { when } from "lit/directives/when.js"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDivider from "@patternfly/patternfly/components/Divider/divider.css"; import PFList from "@patternfly/patternfly/components/List/list.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; @@ -32,8 +38,12 @@ export function versionFamily(): string { return parts.join("."); } +const AdminOverviewBase = WithLicenseSummary(AKElement); + +type Renderer = () => TemplateResult | typeof nothing; + @customElement("ak-admin-overview") -export class AdminOverviewPage extends AKElement { +export class AdminOverviewPage extends AdminOverviewBase { static get styles(): CSSResult[] { return [ PFBase, @@ -41,15 +51,12 @@ export class AdminOverviewPage extends AKElement { PFPage, PFContent, PFList, + PFDivider, css` - .row-divider { - margin-top: -4px; - margin-bottom: -4px; + .pf-l-grid__item { + height: 100%; } - .graph-container { - height: 20em; - } - .big-graph-container { + .pf-l-grid__item.big-graph-container { height: 35em; } .card-container { @@ -74,74 +81,29 @@ export class AdminOverviewPage extends AKElement { } render(): TemplateResult { - let name = this.user?.user.username; - if (this.user?.user.name) { - name = this.user.user.name; - } + const name = this.user?.user.name ?? this.user?.user.username; + return html` ${msg(str`Welcome, ${name}.`)}
-
-
+
+ -
+
-
-
-
-
- -
-
- -
-
- +
+
+ ${this.renderCards()}
-
+
-
-
+
+
`; } + + renderCards() { + const isEnterprise = this.hasEnterpriseLicense; + const classes = { + "card-container": true, + "pf-l-grid__item": true, + "pf-m-6-col": true, + "pf-m-4-col-on-md": !isEnterprise, + "pf-m-4-col-on-xl": !isEnterprise, + "pf-m-3-col-on-md": isEnterprise, + "pf-m-3-col-on-xl": isEnterprise, + }; + + return html`
+ +
+
+ +
+
+ +
+ ${isEnterprise + ? html`
+ +
` + : nothing} `; + } + + renderActions() { + const release = `${versionFamily()}#fixed-in-${VERSION.replaceAll(".", "")}`; + + const quickActions: [string, string][] = [ + [msg("Create a new application"), paramURL("/core/applications", { createForm: true })], + [msg("Check the logs"), paramURL("/events/log")], + [msg("Explore integrations"), "https://goauthentik.io/integrations/"], + [msg("Manage users"), paramURL("/identity/users")], + [msg("Check the release notes"), `https://goauthentik.io/docs/releases/${release}`], + ]; + + const action = ([label, url]: [string, string]) => { + const isExternal = url.startsWith("https://"); + const ex = (truecase: Renderer, falsecase: Renderer) => + when(isExternal, truecase, falsecase); + + const content = html`${label}${ex( + () => html``, + () => nothing, + )}`; + + return html`
  • + ${ex( + () => html`${content}`, + () => html`${content}`, + )} +
  • `; + }; + + return html`${map(quickActions, action)}`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ak-admin-overview": AdminOverviewPage; + } } diff --git a/web/src/admin/admin-overview/DashboardUserPage.ts b/web/src/admin/admin-overview/DashboardUserPage.ts index b3829ed6a8..bbff178b80 100644 --- a/web/src/admin/admin-overview/DashboardUserPage.ts +++ b/web/src/admin/admin-overview/DashboardUserPage.ts @@ -8,6 +8,7 @@ import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement } from "lit/decorators.js"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDivider from "@patternfly/patternfly/components/Divider/divider.css"; import PFList from "@patternfly/patternfly/components/List/list.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; @@ -22,14 +23,8 @@ export class DashboardUserPage extends AKElement { PFPage, PFContent, PFList, + PFDivider, css` - .row-divider { - margin-top: -4px; - margin-bottom: -4px; - } - .graph-container { - height: 20em; - } .big-graph-container { height: 35em; } @@ -59,8 +54,8 @@ export class DashboardUserPage extends AKElement { -
    -
    +
    +
    { + icon = "pf-icon pf-icon-server"; + + @state() + statusSummary?: string; + + async getPrimaryValue(): Promise { + return await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve(); + } + + setStatus(summary: string, content: StatusContent): Promise { + this.statusSummary = summary; + return Promise.resolve(content); + } + + getStatus(value: SystemInfo): Promise { + return value.runtime.opensslFipsEnabled + ? this.setStatus(msg("OK"), { + icon: "fa fa-check-circle pf-m-success", + message: html`${msg("FIPS compliance: passing")}`, + }) + : this.setStatus(msg("Unverified"), { + icon: "fa fa-info-circle pf-m-warning", + message: html`${msg("FIPS compliance: unverified")}`, + }); + } + + renderHeader(): TemplateResult { + return html`${msg("FIPS Status")}`; + } + + renderValue(): TemplateResult { + return html`${this.statusSummary}`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ak-admin-fips-status-system": FipsStatusCard; + } +} diff --git a/web/src/admin/admin-overview/cards/RecentEventsCard.ts b/web/src/admin/admin-overview/cards/RecentEventsCard.ts index 11c197dd81..a67910fffe 100644 --- a/web/src/admin/admin-overview/cards/RecentEventsCard.ts +++ b/web/src/admin/admin-overview/cards/RecentEventsCard.ts @@ -1,8 +1,8 @@ -import { EventGeo } from "@goauthentik/admin/events/utils"; +import { EventGeo, EventUser } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { actionToLabel } from "@goauthentik/common/labels"; -import { truncate } from "@goauthentik/common/utils"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-event-info"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/Dropdown"; @@ -11,7 +11,7 @@ import "@goauthentik/elements/buttons/SpinnerButton"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { Table, TableColumn } from "@goauthentik/elements/table/Table"; -import { msg, str } from "@lit/localize"; +import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; @@ -47,6 +47,9 @@ export class RecentEventsCard extends Table { --pf-c-card__title--FontSize: var(--pf-global--FontSize--md); --pf-c-card__title--FontWeight: var(--pf-global--FontWeight--bold); } + * { + word-break: break-all; + } `, ); } @@ -57,7 +60,7 @@ export class RecentEventsCard extends Table { new TableColumn(msg("User"), "user"), new TableColumn(msg("Creation Date"), "created"), new TableColumn(msg("Client IP"), "client_ip"), - new TableColumn(msg("Tenant"), "tenant_name"), + new TableColumn(msg("Brand"), "brand_name"), ]; } @@ -71,24 +74,12 @@ export class RecentEventsCard extends Table { return [ html` ${item.app}`, - item.user?.username - ? html` - ${item.user.on_behalf_of - ? html` - ${msg(str`On behalf of ${item.user.on_behalf_of.username}`)} - ` - : html``}` - : html`-`, - html`${item.created?.toLocaleString()}`, + EventUser(item), + html`
    ${getRelativeTime(item.created)}
    + ${item.created.toLocaleString()}`, html`
    ${item.clientIp || msg("-")}
    ${EventGeo(item)}`, - html`${item.tenant?.name || msg("-")}`, + html`${item.brand?.name || msg("-")}`, ]; } diff --git a/web/src/admin/admin-overview/cards/SystemStatusCard.ts b/web/src/admin/admin-overview/cards/SystemStatusCard.ts index 7c6ac276c6..ee5449fcf7 100644 --- a/web/src/admin/admin-overview/cards/SystemStatusCard.ts +++ b/web/src/admin/admin-overview/cards/SystemStatusCard.ts @@ -22,7 +22,10 @@ export class SystemStatusCard extends AdminStatusCard { async getPrimaryValue(): Promise { this.now = new Date(); let status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve(); - if (status.embeddedOutpostHost === "" || !status.embeddedOutpostHost.includes("http")) { + if ( + !status.embeddedOutpostDisabled && + (status.embeddedOutpostHost === "" || !status.embeddedOutpostHost.includes("http")) + ) { // First install, ensure the embedded outpost host is set // also run when outpost host does not contain http // (yes it's called host and requires a URL, i know) @@ -51,7 +54,7 @@ export class SystemStatusCard extends AdminStatusCard { } getStatus(value: SystemInfo): Promise { - if (value.embeddedOutpostHost === "") { + if (!value.embeddedOutpostDisabled && value.embeddedOutpostHost === "") { this.statusSummary = msg("Warning"); return Promise.resolve({ icon: "fa fa-exclamation-triangle pf-m-warning", diff --git a/web/src/admin/admin-overview/cards/VersionStatusCard.ts b/web/src/admin/admin-overview/cards/VersionStatusCard.ts index 01689aede7..a8c94cc377 100644 --- a/web/src/admin/admin-overview/cards/VersionStatusCard.ts +++ b/web/src/admin/admin-overview/cards/VersionStatusCard.ts @@ -31,9 +31,15 @@ export class VersionStatusCard extends AdminStatusCard { message: html`${msg(str`${value.versionLatest} is available!`)}`, }); } + if (value.versionLatestValid) { + return Promise.resolve({ + icon: "fa fa-check-circle pf-m-success", + message: html`${msg("Up-to-date!")}`, + }); + } return Promise.resolve({ - icon: "fa fa-check-circle pf-m-success", - message: html`${msg("Up-to-date!")}`, + icon: "fa fa-question-circle", + message: html`${msg("Latest version unknown")}`, }); } diff --git a/web/src/admin/admin-overview/charts/AdminLoginAuthorizeChart.ts b/web/src/admin/admin-overview/charts/AdminLoginAuthorizeChart.ts index 9cf8760ba1..a70140567a 100644 --- a/web/src/admin/admin-overview/charts/AdminLoginAuthorizeChart.ts +++ b/web/src/admin/admin-overview/charts/AdminLoginAuthorizeChart.ts @@ -1,8 +1,8 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKChart, RGBAColor } from "@goauthentik/elements/charts/Chart"; -import { ChartData, Tick } from "chart.js"; +import { ChartData } from "chart.js"; -import { msg, str } from "@lit/localize"; +import { msg } from "@lit/localize"; import { customElement } from "lit/decorators.js"; import { AdminApi, LoginMetrics } from "@goauthentik/api"; @@ -13,13 +13,6 @@ export class AdminLoginAuthorizeChart extends AKChart { return new AdminApi(DEFAULT_CONFIG).adminMetricsRetrieve(); } - timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string { - const valueStamp = ticks[index]; - const delta = Date.now() - valueStamp.value; - const ago = Math.round(delta / 1000 / 3600 / 24); - return msg(str`${ago} day(s) ago`); - } - getChartData(data: LoginMetrics): ChartData { return { datasets: [ diff --git a/web/src/admin/admin-overview/charts/OutpostStatusChart.ts b/web/src/admin/admin-overview/charts/OutpostStatusChart.ts index 586379f033..136863c6da 100644 --- a/web/src/admin/admin-overview/charts/OutpostStatusChart.ts +++ b/web/src/admin/admin-overview/charts/OutpostStatusChart.ts @@ -1,4 +1,4 @@ -import { SyncStatus } from "@goauthentik/admin/admin-overview/charts/SyncStatusChart"; +import { SummarizedSyncStatus } from "@goauthentik/admin/admin-overview/charts/SyncStatusChart"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKChart } from "@goauthentik/elements/charts/Chart"; import "@goauthentik/elements/forms/ConfirmationForm"; @@ -10,7 +10,7 @@ import { customElement } from "lit/decorators.js"; import { OutpostsApi } from "@goauthentik/api"; @customElement("ak-admin-status-chart-outpost") -export class OutpostStatusChart extends AKChart { +export class OutpostStatusChart extends AKChart { getChartType(): string { return "doughnut"; } @@ -26,16 +26,16 @@ export class OutpostStatusChart extends AKChart { }; } - async apiRequest(): Promise { + async apiRequest(): Promise { const api = new OutpostsApi(DEFAULT_CONFIG); const outposts = await api.outpostsInstancesList({}); - const outpostStats: SyncStatus[] = []; + const outpostStats: SummarizedSyncStatus[] = []; await Promise.all( outposts.results.map(async (element) => { const health = await api.outpostsInstancesHealthList({ uuid: element.pk || "", }); - const singleStats: SyncStatus = { + const singleStats: SummarizedSyncStatus = { unsynced: 0, healthy: 0, failed: 0, @@ -59,7 +59,7 @@ export class OutpostStatusChart extends AKChart { return outpostStats; } - getChartData(data: SyncStatus[]): ChartData { + getChartData(data: SummarizedSyncStatus[]): ChartData { return { labels: [msg("Healthy outposts"), msg("Outdated outposts"), msg("Unhealthy outposts")], datasets: data.map((d) => { diff --git a/web/src/admin/admin-overview/charts/SyncStatusChart.ts b/web/src/admin/admin-overview/charts/SyncStatusChart.ts index 28747d6829..6c8887fb2b 100644 --- a/web/src/admin/admin-overview/charts/SyncStatusChart.ts +++ b/web/src/admin/admin-overview/charts/SyncStatusChart.ts @@ -1,14 +1,15 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKChart } from "@goauthentik/elements/charts/Chart"; import "@goauthentik/elements/forms/ConfirmationForm"; +import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { ChartData, ChartOptions } from "chart.js"; import { msg } from "@lit/localize"; import { customElement } from "lit/decorators.js"; -import { ProvidersApi, SourcesApi, TaskStatusEnum } from "@goauthentik/api"; +import { ProvidersApi, SourcesApi, SyncStatus, SystemTaskStatusEnum } from "@goauthentik/api"; -export interface SyncStatus { +export interface SummarizedSyncStatus { healthy: number; failed: number; unsynced: number; @@ -17,7 +18,7 @@ export interface SyncStatus { } @customElement("ak-admin-status-chart-sync") -export class LDAPSyncStatusChart extends AKChart { +export class SyncStatusChart extends AKChart { getChartType(): string { return "doughnut"; } @@ -33,98 +34,104 @@ export class LDAPSyncStatusChart extends AKChart { }; } - async ldapStatus(): Promise { - const api = new SourcesApi(DEFAULT_CONFIG); - const sources = await api.sourcesLdapList({}); + async fetchStatus( + listObjects: () => Promise>, + fetchSyncStatus: (element: T) => Promise, + label: string, + ): Promise { + const objects = await listObjects(); const metrics: { [key: string]: number } = { healthy: 0, failed: 0, unsynced: 0, }; await Promise.all( - sources.results.map(async (element) => { + objects.results.map(async (element) => { + // Each source should have 3 successful tasks, so the worst task overwrites + let objectKey = "healthy"; try { - const health = await api.sourcesLdapSyncStatusRetrieve({ - slug: element.slug, - }); - - health.tasks.forEach((task) => { - if (task.status !== TaskStatusEnum.Successful) { - metrics.failed += 1; + const status = await fetchSyncStatus(element); + status.tasks.forEach((task) => { + if (task.status !== SystemTaskStatusEnum.Successful) { + objectKey = "failed"; } const now = new Date().getTime(); const maxDelta = 3600000; // 1 hour - if (!health || now - task.taskFinishTimestamp.getTime() > maxDelta) { - metrics.unsynced += 1; - } else { - metrics.healthy += 1; + if (!status || now - task.finishTimestamp.getTime() > maxDelta) { + objectKey = "unsynced"; } }); - if (health.tasks.length < 1) { - metrics.unsynced += 1; - } } catch { - metrics.unsynced += 1; + objectKey = "unsynced"; } + metrics[objectKey] += 1; }), ); return { healthy: metrics.healthy, failed: metrics.failed, - unsynced: sources.pagination.count === 0 ? 1 : metrics.unsynced, - total: sources.pagination.count, - label: msg("LDAP Source"), + unsynced: objects.pagination.count === 0 ? 1 : metrics.unsynced, + total: objects.pagination.count, + label: label, }; } - async scimStatus(): Promise { - const api = new ProvidersApi(DEFAULT_CONFIG); - const providers = await api.providersScimList({}); - const metrics: { [key: string]: number } = { - healthy: 0, - failed: 0, - unsynced: 0, - }; - await Promise.all( - providers.results.map(async (element) => { - // Each source should have 3 successful tasks, so the worst task overwrites - let sourceKey = "healthy"; - try { - const health = await api.providersScimSyncStatusRetrieve({ + async apiRequest(): Promise { + const statuses = [ + await this.fetchStatus( + () => { + return new ProvidersApi(DEFAULT_CONFIG).providersScimList(); + }, + (element) => { + return new ProvidersApi(DEFAULT_CONFIG).providersScimSyncStatusRetrieve({ id: element.pk, }); - - if (health.status !== TaskStatusEnum.Successful) { - sourceKey = "failed"; - } - const now = new Date().getTime(); - const maxDelta = 3600000; // 1 hour - if (!health || now - health.taskFinishTimestamp.getTime() > maxDelta) { - sourceKey = "unsynced"; - } - } catch { - sourceKey = "unsynced"; - } - metrics[sourceKey] += 1; - }), - ); - return { - healthy: metrics.healthy, - failed: metrics.failed, - unsynced: providers.pagination.count === 0 ? 1 : metrics.unsynced, - total: providers.pagination.count, - label: msg("SCIM Provider"), - }; + }, + msg("SCIM Provider"), + ), + await this.fetchStatus( + () => { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceList(); + }, + (element) => { + return new ProvidersApi( + DEFAULT_CONFIG, + ).providersGoogleWorkspaceSyncStatusRetrieve({ + id: element.pk, + }); + }, + msg("Google Workspace Provider"), + ), + await this.fetchStatus( + () => { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraList(); + }, + (element) => { + return new ProvidersApi( + DEFAULT_CONFIG, + ).providersMicrosoftEntraSyncStatusRetrieve({ + id: element.pk, + }); + }, + msg("Microsoft Entra Provider"), + ), + await this.fetchStatus( + () => { + return new SourcesApi(DEFAULT_CONFIG).sourcesLdapList(); + }, + (element) => { + return new SourcesApi(DEFAULT_CONFIG).sourcesLdapSyncStatusRetrieve({ + slug: element.slug, + }); + }, + msg("LDAP Source"), + ), + ]; + this.centerText = statuses.reduce((total, el) => (total += el.total), 0).toString(); + return statuses; } - async apiRequest(): Promise { - const ldapStatus = await this.ldapStatus(); - const scimStatus = await this.scimStatus(); - this.centerText = (ldapStatus.total + scimStatus.total).toString(); - return [ldapStatus, scimStatus]; - } - - getChartData(data: SyncStatus[]): ChartData { + getChartData(data: SummarizedSyncStatus[]): ChartData { return { labels: [msg("Healthy"), msg("Failed"), msg("Unsynced / N/A")], datasets: data.map((d) => { diff --git a/web/src/admin/admin-settings/AdminSettingsForm.ts b/web/src/admin/admin-settings/AdminSettingsForm.ts new file mode 100644 index 0000000000..b459945e1e --- /dev/null +++ b/web/src/admin/admin-settings/AdminSettingsForm.ts @@ -0,0 +1,216 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-number-input"; +import "@goauthentik/components/ak-switch-input"; +import "@goauthentik/components/ak-text-input"; +import "@goauthentik/elements/CodeMirror"; +import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import { Form } from "@goauthentik/elements/forms/Form"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import "@goauthentik/elements/forms/Radio"; +import "@goauthentik/elements/forms/SearchSelect"; +import "@goauthentik/elements/utils/TimeDeltaHelp"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import PFList from "@patternfly/patternfly/components/List/list.css"; + +import { AdminApi, Settings, SettingsRequest } from "@goauthentik/api"; + +@customElement("ak-admin-settings-form") +export class AdminSettingsForm extends Form { + // + // Custom property accessors in Lit 2 require a manual call to requestUpdate(). See: + // https://lit.dev/docs/v2/components/properties/#accessors-custom + // + set settings(value: Settings | undefined) { + this._settings = value; + this.requestUpdate(); + } + + @property({ type: Object }) + get settings() { + return this._settings; + } + + private _settings?: Settings; + + static get styles(): CSSResult[] { + return super.styles.concat(PFList); + } + + getSuccessMessage(): string { + return msg("Successfully updated settings."); + } + + async send(data: SettingsRequest): Promise { + const result = await new AdminApi(DEFAULT_CONFIG).adminSettingsUpdate({ + settingsRequest: data, + }); + this.dispatchEvent(new CustomEvent("ak-admin-setting-changed")); + return result; + } + + renderForm(): TemplateResult { + return html` + + ${msg( + "Configure how authentik should show avatars for users. The following values can be set:", + )} +

    +
      +
    • + none: + ${msg( + "Disables per-user avatars and just shows a 1x1 pixel transparent picture", + )} +
    • +
    • + gravatar: + ${msg("Uses gravatar with the user's email address")} +
    • +
    • + initials: + ${msg("Generated avatars based on the user's name")} +
    • +
    • + ${msg( + "Any URL: If you want to use images hosted on another server, you can set any URL. Additionally, these placeholders can be used:", + )} +
        +
      • + %(username)s: ${msg("The user's username")} +
      • +
      • + %(mail_hash)s: + ${msg("The email address, md5 hashed")} +
      • +
      • + %(upn)s: + ${msg("The user's UPN, if set (otherwise an empty string)")} +
      • +
      +
    • +
    • + ${msg( + html`An attribute path like + attributes.something.avatar, which can be used in + combination with the file field to allow users to upload custom + avatars for themselves.`, + )} +
    • +
    +

    + ${msg( + "Multiple values can be set, comma-separated, and authentik will fallback to the next mode when no avatar could be found.", + )} + ${msg( + html`For example, setting this to gravatar,initials will + attempt to get an avatar from Gravatar, and if the user has not + configured on there, it will fallback to a generated avatar.`, + )} +

    + `} + required + > +
    + + + + + + + + ${msg("Duration after which events will be deleted from the database.")} +

    +

    + ${msg( + 'When using an external logging solution for archiving, this can be set to "minutes=5".', + )} +

    +

    + ${msg( + "This setting only affects new Events, as the expiration is saved per-event.", + )} +

    + `} + > +
    + + +

    + ${msg( + "This option configures the footer links on the flow executor pages. It must be a valid YAML or JSON list and can be used as follows:", + )} + [{"name": "Link Name","href":"https://goauthentik.io"}] +

    +
    + + + + + + ${msg("Default duration for generated tokens")} +

    + `} + > +
    + + `; + } +} diff --git a/web/src/admin/admin-settings/AdminSettingsPage.ts b/web/src/admin/admin-settings/AdminSettingsPage.ts new file mode 100644 index 0000000000..07113a543b --- /dev/null +++ b/web/src/admin/admin-settings/AdminSettingsPage.ts @@ -0,0 +1,112 @@ +import "@goauthentik/admin/admin-settings/AdminSettingsForm"; +import { AdminSettingsForm } from "@goauthentik/admin/admin-settings/AdminSettingsForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/components/events/ObjectChangelog"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/EmptyState"; +import "@goauthentik/elements/Markdown"; +import "@goauthentik/elements/PageHeader"; +import "@goauthentik/elements/Tabs"; +import "@goauthentik/elements/buttons/ModalButton"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/forms/ModalForm"; + +import { msg } from "@lit/localize"; +import { html, nothing } from "lit"; +import { customElement, query, state } from "lit/decorators.js"; + +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { AdminApi, Settings } from "@goauthentik/api"; + +@customElement("ak-admin-settings") +export class AdminSettingsPage extends AKElement { + static get styles() { + return [ + PFBase, + PFButton, + PFPage, + PFGrid, + PFContent, + PFCard, + PFDescriptionList, + PFForm, + PFFormControl, + PFBanner, + ]; + } + + @query("ak-admin-settings-form#form") + form?: AdminSettingsForm; + + @state() + settings?: Settings; + + constructor() { + super(); + AdminSettingsPage.fetchSettings().then((settings) => { + this.settings = settings; + }); + this.save = this.save.bind(this); + this.reset = this.reset.bind(this); + this.addEventListener("ak-admin-setting-changed", this.handleUpdate.bind(this)); + } + + static async fetchSettings() { + return await new AdminApi(DEFAULT_CONFIG).adminSettingsRetrieve(); + } + + async handleUpdate() { + this.settings = await AdminSettingsPage.fetchSettings(); + } + + async save() { + if (!this.form) { + return; + } + await this.form.submit(new Event("submit")); + this.settings = await AdminSettingsPage.fetchSettings(); + } + + async reset() { + this.form?.resetForm(); + } + + render() { + if (!this.settings) { + return nothing; + } + return html` + + ${msg("System settings")} + +
    +
    +
    + + +
    + +
    +
    + `; + } +} diff --git a/web/src/admin/applications/ApplicationCheckAccessForm.ts b/web/src/admin/applications/ApplicationCheckAccessForm.ts index 7f1c18e17d..7a2e1f7dab 100644 --- a/web/src/admin/applications/ApplicationCheckAccessForm.ts +++ b/web/src/admin/applications/ApplicationCheckAccessForm.ts @@ -1,5 +1,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/components/ak-status-label"; +import "@goauthentik/elements/events/LogViewer"; import { Form } from "@goauthentik/elements/forms/Form"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -83,28 +84,7 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
    - ${(this.result?.logMessages || []).length > 0 - ? this.result?.logMessages?.map((m) => { - return html`
    -
    - ${m.log_level} -
    -
    -
    - ${m.event} -
    -
    -
    `; - }) - : html`
    -
    - ${msg("No log messages.")} -
    -
    `} +
    diff --git a/web/src/admin/applications/ApplicationForm.ts b/web/src/admin/applications/ApplicationForm.ts index 970b3638e9..ead17e9b23 100644 --- a/web/src/admin/applications/ApplicationForm.ts +++ b/web/src/admin/applications/ApplicationForm.ts @@ -1,13 +1,16 @@ import "@goauthentik/admin/applications/ProviderSelectModal"; import { iconHelperText } from "@goauthentik/admin/helperText"; -import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-file-input"; import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-textarea-input"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/ModalForm"; @@ -22,13 +25,7 @@ import { TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { - Application, - CapabilitiesEnum, - CoreApi, - PolicyEngineMode, - Provider, -} from "@goauthentik/api"; +import { Application, CoreApi, PolicyEngineMode, Provider } from "@goauthentik/api"; import "./components/ak-backchannel-input"; import "./components/ak-provider-search-input"; @@ -48,7 +45,7 @@ export const policyOptions = [ ]; @customElement("ak-application-form") -export class ApplicationForm extends ModelForm { +export class ApplicationForm extends WithCapabilitiesConfig(ModelForm) { constructor() { super(); this.handleConfirmBackchannelProviders = this.handleConfirmBackchannelProviders.bind(this); @@ -93,8 +90,7 @@ export class ApplicationForm extends ModelForm { applicationRequest: data, }); } - const c = await config(); - if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) { + if (this.can(CapabilitiesEnum.CanSaveMedia)) { const icon = this.getFormFiles()["metaIcon"]; if (icon || this.clearIcon) { await new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({ @@ -140,21 +136,21 @@ export class ApplicationForm extends ModelForm { return html` { @@ -209,11 +205,11 @@ export class ApplicationForm extends ModelForm { )} > - ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` ${this.instance?.metaIcon diff --git a/web/src/admin/applications/ApplicationListPage.ts b/web/src/admin/applications/ApplicationListPage.ts index fd8306c3f6..84718ef2f0 100644 --- a/web/src/admin/applications/ApplicationListPage.ts +++ b/web/src/admin/applications/ApplicationListPage.ts @@ -1,15 +1,14 @@ import "@goauthentik/admin/applications/ApplicationForm"; -import { PFSize } from "@goauthentik/app/elements/Spinner"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { PFSize } from "@goauthentik/common/enums.js"; import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/components/ak-app-icon"; -import MDApplication from "@goauthentik/docs/core/applications.md"; +import MDApplication from "@goauthentik/docs/applications/index.md"; import "@goauthentik/elements/Markdown"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; -// import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -25,6 +24,22 @@ import { Application, CoreApi } from "@goauthentik/api"; import "./ApplicationWizardHint"; +export const applicationListStyle = css` + /* Fix alignment issues with images in tables */ + .pf-c-table tbody > tr > * { + vertical-align: middle; + } + tr td:first-child { + width: auto; + min-width: 0px; + text-align: center; + vertical-align: middle; + } + .pf-c-sidebar.pf-m-gutter > .pf-c-sidebar__main > * + * { + margin-left: calc(var(--pf-c-sidebar__main--child--MarginLeft) / 2); + } +`; + @customElement("ak-application-list") export class ApplicationListPage extends TablePage { searchEnabled(): boolean { @@ -43,6 +58,7 @@ export class ApplicationListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "name"; @@ -58,24 +74,7 @@ export class ApplicationListPage extends TablePage { } static get styles(): CSSResult[] { - return super.styles.concat( - PFCard, - css` - /* Fix alignment issues with images in tables */ - .pf-c-table tbody > tr > * { - vertical-align: middle; - } - tr td:first-child { - width: auto; - min-width: 0px; - text-align: center; - vertical-align: middle; - } - .pf-c-sidebar.pf-m-gutter > .pf-c-sidebar__main > * + * { - margin-left: calc(var(--pf-c-sidebar__main--child--MarginLeft) / 2); - } - `, - ); + return super.styles.concat(PFCard, applicationListStyle); } columns(): TableColumn[] { @@ -94,13 +93,10 @@ export class ApplicationListPage extends TablePage { } renderSidebarAfter(): TemplateResult { - // Rendering the wizard with .open here, as if we set the attribute in - // renderObjectCreate() it'll open two wizards, since that function gets called twice - return html`
    - +
    `; diff --git a/web/src/admin/applications/ApplicationViewPage.ts b/web/src/admin/applications/ApplicationViewPage.ts index 84ea2f207a..19db71df55 100644 --- a/web/src/admin/applications/ApplicationViewPage.ts +++ b/web/src/admin/applications/ApplicationViewPage.ts @@ -2,9 +2,8 @@ import "@goauthentik/admin/applications/ApplicationAuthorizeChart"; import "@goauthentik/admin/applications/ApplicationCheckAccessForm"; import "@goauthentik/admin/applications/ApplicationForm"; import "@goauthentik/admin/policies/BoundPoliciesList"; -import { PFSize } from "@goauthentik/app/elements/Spinner"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { PFSize } from "@goauthentik/common/enums.js"; import "@goauthentik/components/ak-app-icon"; import "@goauthentik/components/events/ObjectChangelog"; import { AKElement } from "@goauthentik/elements/Base"; @@ -12,9 +11,10 @@ import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -37,37 +37,11 @@ import { @customElement("ak-application-view") export class ApplicationViewPage extends AKElement { - @property() - set applicationSlug(value: string) { - new CoreApi(DEFAULT_CONFIG) - .coreApplicationsRetrieve({ - slug: value, - }) - .then((app) => { - this.application = app; - if ( - app.providerObj && - [ - "authentik_providers_proxy.proxyprovider", - "authentik_providers_ldap.ldapprovider", - ].includes(app.providerObj.metaModelName) - ) { - new OutpostsApi(DEFAULT_CONFIG) - .outpostsInstancesList({ - providersByPk: [app.provider || 0], - pageSize: 1, - }) - .then((outposts) => { - if (outposts.pagination.count < 1) { - this.missingOutpost = true; - } - }); - } - }); - } + @property({ type: String }) + applicationSlug?: string; - @property({ attribute: false }) - application!: Application; + @state() + application?: Application; @state() missingOutpost = false; @@ -86,6 +60,40 @@ export class ApplicationViewPage extends AKElement { ]; } + fetchIsMissingOutpost(providersByPk: Array) { + new OutpostsApi(DEFAULT_CONFIG) + .outpostsInstancesList({ + providersByPk, + pageSize: 1, + }) + .then((outposts) => { + if (outposts.pagination.count < 1) { + this.missingOutpost = true; + } + }); + } + + fetchApplication(slug: string) { + new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({ slug }).then((app) => { + this.application = app; + if ( + app.providerObj && + [ + RbacPermissionsAssignedByUsersListModelEnum.ProvidersProxyProxyprovider.toString(), + RbacPermissionsAssignedByUsersListModelEnum.ProvidersLdapLdapprovider.toString(), + ].includes(app.providerObj.metaModelName) + ) { + this.fetchIsMissingOutpost([app.provider || 0]); + } + }); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("applicationSlug") && this.applicationSlug) { + this.fetchApplication(this.applicationSlug); + } + } + render(): TemplateResult { return html` { } @property({ type: Boolean }) - backchannelOnly = false; + backchannel?: boolean; @property() confirm!: (selectedItems: Provider[]) => Promise; @@ -34,7 +34,7 @@ export class ProviderSelectModal extends TableModal { page: page, pageSize: (await uiConfig()).pagination.perPage, search: this.search || "", - backchannelOnly: this.backchannelOnly, + backchannel: this.backchannel, }); } diff --git a/web/src/admin/applications/components/ak-backchannel-input.ts b/web/src/admin/applications/components/ak-backchannel-input.ts index 06fccc32b1..33b6ef7bb8 100644 --- a/web/src/admin/applications/components/ak-backchannel-input.ts +++ b/web/src/admin/applications/components/ak-backchannel-input.ts @@ -1,5 +1,7 @@ import "@goauthentik/admin/applications/ProviderSelectModal"; import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/chips/Chip"; +import "@goauthentik/elements/chips/ChipGroup"; import { TemplateResult, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; @@ -20,8 +22,9 @@ export class AkBackchannelProvidersInput extends AKElement { // TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the // visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in // general. + protected createRenderRoot() { - return this; + return this as HTMLElement; } @property({ type: String }) @@ -61,9 +64,9 @@ export class AkBackchannelProvidersInput extends AKElement { >`; return html` - +
    - +
    - ${this.help ? html`

    ${this.help}

    ` : nothing} + ${this.help ? html`

    ${this.help}

    ` : nothing} `; } diff --git a/web/src/admin/applications/components/ak-provider-search-input.ts b/web/src/admin/applications/components/ak-provider-search-input.ts index 552cb0764a..57752f85df 100644 --- a/web/src/admin/applications/components/ak-provider-search-input.ts +++ b/web/src/admin/applications/components/ak-provider-search-input.ts @@ -15,6 +15,7 @@ const doGroupBy = (items: Provider[]) => groupBy(items, (item) => item.verboseNa async function fetch(query?: string) { const args: ProvidersAllListRequest = { ordering: "name", + backchannel: false, }; if (query !== undefined) { args.search = query; diff --git a/web/src/admin/applications/wizard/BasePanel.ts b/web/src/admin/applications/wizard/BasePanel.ts index 1b2db5bd40..1f4bf37161 100644 --- a/web/src/admin/applications/wizard/BasePanel.ts +++ b/web/src/admin/applications/wizard/BasePanel.ts @@ -4,7 +4,7 @@ import { KeyUnknown, serializeForm } from "@goauthentik/elements/forms/Form"; import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; -import { consume } from "@lit-labs/context"; +import { consume } from "@lit/context"; import { query } from "@lit/reactive-element/decorators.js"; import { styles as AwadStyles } from "./BasePanel.css"; diff --git a/web/src/admin/applications/wizard/ContextIdentity.ts b/web/src/admin/applications/wizard/ContextIdentity.ts index 629b65cd84..ab71861b70 100644 --- a/web/src/admin/applications/wizard/ContextIdentity.ts +++ b/web/src/admin/applications/wizard/ContextIdentity.ts @@ -1,4 +1,4 @@ -import { createContext } from "@lit-labs/context"; +import { createContext } from "@lit/context"; import { ApplicationWizardState } from "./types"; diff --git a/web/src/admin/applications/wizard/ak-application-wizard.ts b/web/src/admin/applications/wizard/ak-application-wizard.ts index 50cf750d7d..1e33124fec 100644 --- a/web/src/admin/applications/wizard/ak-application-wizard.ts +++ b/web/src/admin/applications/wizard/ak-application-wizard.ts @@ -1,7 +1,7 @@ import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard"; import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; -import { ContextProvider } from "@lit-labs/context"; +import { ContextProvider } from "@lit/context"; import { msg } from "@lit/localize"; import { customElement, state } from "lit/decorators.js"; diff --git a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts index 2c04429152..b0f1dfa83a 100644 --- a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts +++ b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.choices.ts @@ -1,3 +1,5 @@ +import "@goauthentik/admin/common/ak-license-notice"; + import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; @@ -8,6 +10,7 @@ import type { ModelRequest, OAuth2ProviderRequest, ProxyProviderRequest, + RACProviderRequest, RadiusProviderRequest, SAMLProviderRequest, SCIMProviderRequest, @@ -19,150 +22,155 @@ type ProviderRenderer = () => TemplateResult; type ModelConverter = (provider: OneOfProvider) => ModelRequest; -/** - * There's an internal key and an API key because "Proxy" has three different subtypes. - */ -// prettier-ignore -type ProviderType = [ - string, // internal key used by the wizard to distinguish between providers - string, // Name of the provider - string, // Description - ProviderRenderer, // Function that returns the provider's wizard panel as a TemplateResult - ProviderModelEnumType, // key used by the API to distinguish between providers - ModelConverter, // Handler that takes a generic provider and returns one specifically typed to its panel -]; +type ProviderNoteProvider = () => TemplateResult | undefined; +type ProviderNote = ProviderNoteProvider | undefined; export type LocalTypeCreate = TypeCreate & { formName: string; modelName: ProviderModelEnumType; converter: ModelConverter; + note?: ProviderNote; + renderer: ProviderRenderer; }; -// prettier-ignore -const _providerModelsTable: ProviderType[] = [ - [ - "oauth2provider", - msg("OAuth2/OIDC (Open Authorization/OpenID Connect)"), - msg("Modern applications, APIs and Single-page applications."), - () => +export const providerModelsList: LocalTypeCreate[] = [ + { + formName: "oauth2provider", + name: msg("OAuth2/OIDC (Open Authorization/OpenID Connect)"), + description: msg("Modern applications, APIs and Single-page applications."), + renderer: () => html``, - ProviderModelEnum.Oauth2Oauth2provider, - (provider: OneOfProvider) => ({ + modelName: ProviderModelEnum.Oauth2Oauth2provider, + converter: (provider: OneOfProvider) => ({ providerModel: ProviderModelEnum.Oauth2Oauth2provider, ...(provider as OAuth2ProviderRequest), }), - ], - [ - "ldapprovider", - msg("LDAP (Lightweight Directory Access Protocol)"), - msg("Provide an LDAP interface for applications and users to authenticate against."), - () => + component: "", + iconUrl: "/static/authentik/sources/openidconnect.svg", + }, + { + formName: "ldapprovider", + name: msg("LDAP (Lightweight Directory Access Protocol)"), + description: msg( + "Provide an LDAP interface for applications and users to authenticate against.", + ), + renderer: () => html``, - ProviderModelEnum.LdapLdapprovider, - (provider: OneOfProvider) => ({ + modelName: ProviderModelEnum.LdapLdapprovider, + converter: (provider: OneOfProvider) => ({ providerModel: ProviderModelEnum.LdapLdapprovider, ...(provider as LDAPProviderRequest), }), - ], - [ - "proxyprovider-proxy", - msg("Transparent Reverse Proxy"), - msg("For transparent reverse proxies with required authentication"), - () => + component: "", + iconUrl: "/static/authentik/sources/ldap.png", + }, + { + formName: "proxyprovider-proxy", + name: msg("Transparent Reverse Proxy"), + description: msg("For transparent reverse proxies with required authentication"), + renderer: () => html``, - ProviderModelEnum.ProxyProxyprovider, - (provider: OneOfProvider) => ({ + modelName: ProviderModelEnum.ProxyProxyprovider, + converter: (provider: OneOfProvider) => ({ providerModel: ProviderModelEnum.ProxyProxyprovider, ...(provider as ProxyProviderRequest), mode: ProxyMode.Proxy, }), - ], - [ - "proxyprovider-forwardsingle", - msg("Forward Auth (Single Application)"), - msg("For nginx's auth_request or traefik's forwardAuth"), - () => + component: "", + iconUrl: "/static/authentik/sources/proxy.svg", + }, + { + formName: "proxyprovider-forwardsingle", + name: msg("Forward Auth (Single Application)"), + description: msg("For nginx's auth_request or traefik's forwardAuth"), + renderer: () => html``, - ProviderModelEnum.ProxyProxyprovider, - (provider: OneOfProvider) => ({ + modelName: ProviderModelEnum.ProxyProxyprovider, + converter: (provider: OneOfProvider) => ({ providerModel: ProviderModelEnum.ProxyProxyprovider, ...(provider as ProxyProviderRequest), mode: ProxyMode.ForwardSingle, }), - ], - [ - "proxyprovider-forwarddomain", - msg("Forward Auth (Domain Level)"), - msg("For nginx's auth_request or traefik's forwardAuth per root domain"), - () => + component: "", + iconUrl: "/static/authentik/sources/proxy.svg", + }, + { + formName: "proxyprovider-forwarddomain", + name: msg("Forward Auth (Domain Level)"), + description: msg("For nginx's auth_request or traefik's forwardAuth per root domain"), + renderer: () => html``, - ProviderModelEnum.ProxyProxyprovider, - (provider: OneOfProvider) => ({ + modelName: ProviderModelEnum.ProxyProxyprovider, + converter: (provider: OneOfProvider) => ({ providerModel: ProviderModelEnum.ProxyProxyprovider, ...(provider as ProxyProviderRequest), mode: ProxyMode.ForwardDomain, }), - ], - [ - "samlprovider", - msg("SAML (Security Assertion Markup Language)"), - msg("Configure SAML provider manually"), - () => + component: "", + iconUrl: "/static/authentik/sources/proxy.svg", + }, + { + formName: "racprovider", + name: msg("Remote Access Provider"), + description: msg("Remotely access computers/servers via RDP/SSH/VNC"), + renderer: () => + html``, + modelName: ProviderModelEnum.RacRacprovider, + converter: (provider: OneOfProvider) => ({ + providerModel: ProviderModelEnum.RacRacprovider, + ...(provider as RACProviderRequest), + }), + note: () => html``, + requiresEnterprise: true, + component: "", + iconUrl: "/static/authentik/sources/rac.svg", + }, + { + formName: "samlprovider", + name: msg("SAML (Security Assertion Markup Language)"), + description: msg("Configure SAML provider manually"), + renderer: () => html``, - ProviderModelEnum.SamlSamlprovider, - (provider: OneOfProvider) => ({ + modelName: ProviderModelEnum.SamlSamlprovider, + converter: (provider: OneOfProvider) => ({ providerModel: ProviderModelEnum.SamlSamlprovider, ...(provider as SAMLProviderRequest), }), - ], - [ - "radiusprovider", - msg("RADIUS (Remote Authentication Dial-In User Service)"), - msg("Configure RADIUS provider manually"), - () => + component: "", + iconUrl: "/static/authentik/sources/saml.png", + }, + { + formName: "radiusprovider", + name: msg("RADIUS (Remote Authentication Dial-In User Service)"), + description: msg("Configure RADIUS provider manually"), + renderer: () => html``, - ProviderModelEnum.RadiusRadiusprovider, - (provider: OneOfProvider) => ({ + modelName: ProviderModelEnum.RadiusRadiusprovider, + converter: (provider: OneOfProvider) => ({ providerModel: ProviderModelEnum.RadiusRadiusprovider, ...(provider as RadiusProviderRequest), }), - ], - [ - "scimprovider", - msg("SCIM (System for Cross-domain Identity Management)"), - msg("Configure SCIM provider manually"), - () => + component: "", + iconUrl: "/static/authentik/sources/radius.svg", + }, + { + formName: "scimprovider", + name: msg("SCIM (System for Cross-domain Identity Management)"), + description: msg("Configure SCIM provider manually"), + renderer: () => html``, - ProviderModelEnum.ScimScimprovider, - (provider: OneOfProvider) => ({ + modelName: ProviderModelEnum.ScimScimprovider, + converter: (provider: OneOfProvider) => ({ providerModel: ProviderModelEnum.ScimScimprovider, ...(provider as SCIMProviderRequest), }), - ], + component: "", + iconUrl: "/static/authentik/sources/scim.png", + }, ]; -function mapProviders([ - formName, - name, - description, - _, - modelName, - converter, -]: ProviderType): LocalTypeCreate { - return { - formName, - name, - description, - component: "", - modelName, - converter, - }; -} - -export const providerModelsList = _providerModelsTable.map(mapProviders); - export const providerRendererList = new Map( - _providerModelsTable.map(([modelName, _0, _1, renderer]) => [modelName, renderer]), + providerModelsList.map((tc) => [tc.formName, tc.renderer]), ); export default providerModelsList; diff --git a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts index e13e11ecae..9c89baf94f 100644 --- a/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts +++ b/web/src/admin/applications/wizard/auth-method-choice/ak-application-wizard-authentication-method-choice.ts @@ -1,61 +1,43 @@ import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-text-input"; -import "@goauthentik/elements/forms/FormGroup"; +import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; +import "@goauthentik/elements/wizard/TypeCreateWizardPage"; +import { TypeCreateWizardPageLayouts } from "@goauthentik/elements/wizard/TypeCreateWizardPage"; import { msg } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { html } from "lit"; -import { map } from "lit/directives/map.js"; import BasePanel from "../BasePanel"; -import providerModelsList from "./ak-application-wizard-authentication-method-choice.choices"; import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices"; +import providerModelsList from "./ak-application-wizard-authentication-method-choice.choices"; @customElement("ak-application-wizard-authentication-method-choice") -export class ApplicationWizardAuthenticationMethodChoice extends BasePanel { - constructor() { - super(); - this.handleChoice = this.handleChoice.bind(this); - this.renderProvider = this.renderProvider.bind(this); - } - - handleChoice(ev: InputEvent) { - const target = ev.target as HTMLInputElement; - this.dispatchWizardUpdate({ - update: { - ...this.wizard, - providerModel: target.value, - errors: {}, - }, - status: this.valid ? "valid" : "invalid", - }); - } - - renderProvider(type: LocalTypeCreate) { - const method = this.wizard.providerModel; - - return html`
    - - - ${type.description} -
    `; - } - +export class ApplicationWizardAuthenticationMethodChoice extends WithLicenseSummary(BasePanel) { render() { + const selectedTypes = providerModelsList.filter( + (t) => t.formName === this.wizard.providerModel, + ); return providerModelsList.length > 0 ? html` - ${map(providerModelsList, this.renderProvider)} + 0 ? selectedTypes[0] : undefined} + @select=${(ev: CustomEvent) => { + this.dispatchWizardUpdate({ + update: { + ...this.wizard, + providerModel: ev.detail.formName, + errors: {}, + }, + status: this.valid ? "valid" : "invalid", + }); + }} + > ` : html``; } diff --git a/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts b/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts index 2258aa7b27..c3c7f9d336 100644 --- a/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts +++ b/web/src/admin/applications/wizard/commit/ak-application-wizard-commit-application.ts @@ -1,5 +1,5 @@ -import { EVENT_REFRESH } from "@goauthentik/app/common/constants"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-text-input"; @@ -9,7 +9,7 @@ import "@goauthentik/elements/forms/HorizontalFormElement"; import { msg } from "@lit/localize"; import { customElement, state } from "@lit/reactive-element/decorators.js"; -import { TemplateResult, css, html, nothing } from "lit"; +import { PropertyValues, TemplateResult, css, html, nothing } from "lit"; import { classMap } from "lit/directives/class-map.js"; import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; @@ -94,8 +94,7 @@ export class ApplicationWizardCommitApplication extends BasePanel { response?: TransactionApplicationResponse; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - willUpdate(_changedProperties: Map) { + willUpdate(_changedProperties: PropertyValues) { if (this.commitState === idleState) { this.response = undefined; this.commitState = runningState; diff --git a/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts b/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts index 9b7e813bfc..28139bc465 100644 --- a/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts +++ b/web/src/admin/applications/wizard/methods/ak-application-wizard-authentication-method.ts @@ -7,12 +7,11 @@ import "./oauth/ak-application-wizard-authentication-by-oauth"; import "./proxy/ak-application-wizard-authentication-for-forward-domain-proxy"; import "./proxy/ak-application-wizard-authentication-for-reverse-proxy"; import "./proxy/ak-application-wizard-authentication-for-single-forward-proxy"; +import "./rac/ak-application-wizard-authentication-for-rac"; import "./radius/ak-application-wizard-authentication-by-radius"; import "./saml/ak-application-wizard-authentication-by-saml-configuration"; import "./scim/ak-application-wizard-authentication-by-scim"; -// prettier-ignore - @customElement("ak-application-wizard-authentication-method") export class ApplicationWizardApplicationDetails extends BasePanel { render() { diff --git a/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts b/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts index a99384171e..c4a0b6c06d 100644 --- a/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts +++ b/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts @@ -1,13 +1,13 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/common/ak-core-group-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search"; -import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-number-input"; import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-text-input"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; @@ -32,7 +32,7 @@ import { } from "./LDAPOptionsAndHelp"; @customElement("ak-application-wizard-authentication-by-ldap") -export class ApplicationWizardApplicationDetails extends BaseProviderPanel { +export class ApplicationWizardApplicationDetails extends WithBrandConfig(BaseProviderPanel) { render() { const provider = this.wizard.provider as LDAPProvider | undefined; const errors = this.wizard.errors.provider; @@ -54,12 +54,12 @@ export class ApplicationWizardApplicationDetails extends BaseProviderPanel { name="authorizationFlow" .errorMessages=${errors?.authorizationFlow ?? []} > - + >

    ${msg("Flow used for users to authenticate.")}

    diff --git a/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts b/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts index d9e23e4a25..54bc7d2eae 100644 --- a/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts +++ b/web/src/admin/applications/wizard/methods/oauth/ak-application-wizard-authentication-by-oauth.ts @@ -1,8 +1,9 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/common/ak-crypto-certificate-search"; -import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import { clientTypeOptions, + defaultScopes, issuerModeOptions, redirectUriHelp, subjectModeOptions, @@ -225,10 +226,9 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel { ${this.propertyMappings?.results.map((scope) => { let selected = false; if (!provider?.propertyMappings) { - selected = - scope.managed?.startsWith( - "goauthentik.io/providers/oauth2/scope-", - ) || false; + selected = scope.managed + ? defaultScopes.includes(scope.managed) + : false; } else { selected = Array.from(provider?.propertyMappings).some( (su) => { diff --git a/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts b/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts index 90d0f75806..2be3f8f104 100644 --- a/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts +++ b/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts @@ -183,7 +183,6 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel { { + this.propertyMappings = propertyMappings; + }); + } + + render() { + const provider = this.wizard.provider as RACProvider | undefined; + const selected = new Set(Array.from(provider?.propertyMappings ?? [])); + const errors = this.wizard.errors.provider; + + return html`${msg("Configure Remote Access Provider Provider")} +
    + + + + +

    + ${msg("Flow used when authorizing this provider.")} +

    +
    + + + + + ${msg("Protocol settings")} +
    + + +

    + ${msg("Hold control/command to select multiple items.")} +

    +
    +
    +
    +
    `; + } +} + +export default ApplicationWizardAuthenticationByRAC; diff --git a/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts b/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts index cadbd94ad6..854e2570bb 100644 --- a/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts +++ b/web/src/admin/applications/wizard/methods/radius/ak-application-wizard-authentication-by-radius.ts @@ -1,9 +1,9 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/common/ak-crypto-certificate-search"; -import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-text-input"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; @@ -17,7 +17,7 @@ import { FlowsInstancesListDesignationEnum, RadiusProvider } from "@goauthentik/ import BaseProviderPanel from "../BaseProviderPanel"; @customElement("ak-application-wizard-authentication-by-radius") -export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel { +export class ApplicationWizardAuthenticationByRadius extends WithBrandConfig(BaseProviderPanel) { render() { const provider = this.wizard.provider as RadiusProvider | undefined; const errors = this.wizard.errors.provider; @@ -39,12 +39,12 @@ export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel { name="authorizationFlow" .errorMessages=${errors?.authorizationFlow ?? []} > - + >

    ${msg("Flow used for users to authenticate.")}

    diff --git a/web/src/admin/applications/wizard/methods/saml/SamlProviderOptions.ts b/web/src/admin/applications/wizard/methods/saml/SamlProviderOptions.ts index a1b6d44a27..cf6181f7e3 100644 --- a/web/src/admin/applications/wizard/methods/saml/SamlProviderOptions.ts +++ b/web/src/admin/applications/wizard/methods/saml/SamlProviderOptions.ts @@ -29,5 +29,9 @@ export const signatureAlgorithmOptions = toOptions([ ["RSA-SHA256", SignatureAlgorithmEnum._200104XmldsigMorersaSha256, true], ["RSA-SHA384", SignatureAlgorithmEnum._200104XmldsigMorersaSha384], ["RSA-SHA512", SignatureAlgorithmEnum._200104XmldsigMorersaSha512], + ["ECDSA-SHA1", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha1], + ["ECDSA-SHA256", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha256], + ["ECDSA-SHA384", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha384], + ["ECDSA-SHA512", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha512], ["DSA-SHA1", SignatureAlgorithmEnum._200009XmldsigdsaSha1], ]); diff --git a/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts index 68041c7c12..05a27cd735 100644 --- a/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts +++ b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts @@ -2,7 +2,7 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/common/ak-core-group-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search"; -import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/components/ak-multi-select"; import "@goauthentik/components/ak-number-input"; @@ -194,7 +194,6 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane diff --git a/web/src/admin/applications/wizard/methods/saml/saml-property-mappings-search.ts b/web/src/admin/applications/wizard/methods/saml/saml-property-mappings-search.ts index 27b1d53abc..0e135affb5 100644 --- a/web/src/admin/applications/wizard/methods/saml/saml-property-mappings-search.ts +++ b/web/src/admin/applications/wizard/methods/saml/saml-property-mappings-search.ts @@ -65,7 +65,6 @@ export class SAMLPropertyMappingSearch extends CustomListenerElement(AKElement) super(); this.selected = this.selected.bind(this); this.handleSearchUpdate = this.handleSearchUpdate.bind(this); - this.addCustomListener("ak-change", this.handleSearchUpdate); } get value() { @@ -102,6 +101,7 @@ export class SAMLPropertyMappingSearch extends CustomListenerElement(AKElement) .renderElement=${renderElement} .value=${renderValue} .selected=${this.selected} + @ak-change=${this.handleSearchUpdate} blankable > diff --git a/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts b/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts index 832e7348ae..088cf7992a 100644 --- a/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts +++ b/web/src/admin/applications/wizard/methods/scim/ak-application-wizard-authentication-by-scim.ts @@ -1,7 +1,7 @@ import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/common/ak-core-group-search"; import "@goauthentik/admin/common/ak-crypto-certificate-search"; -import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-multi-select"; @@ -123,7 +123,6 @@ export class ApplicationWizardAuthenticationBySCIM extends BaseProviderPanel {
    | Partial | Partial + | Partial | Partial | Partial | Partial diff --git a/web/src/admin/blueprints/BlueprintListPage.ts b/web/src/admin/blueprints/BlueprintListPage.ts index 188bb4be58..6eb24aa2ee 100644 --- a/web/src/admin/blueprints/BlueprintListPage.ts +++ b/web/src/admin/blueprints/BlueprintListPage.ts @@ -2,6 +2,7 @@ import "@goauthentik/admin/blueprints/BlueprintForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/SpinnerButton"; @@ -58,6 +59,7 @@ export class BlueprintListPage extends TablePage { expandable = true; checkbox = true; + clearOnRefresh = true; @property() order = "name"; @@ -143,7 +145,8 @@ export class BlueprintListPage extends TablePage { html`
    ${item.name}
    ${description ? html`${description}` : html``}`, html`${BlueprintStatus(item)}`, - html`${item.lastApplied.toLocaleString()}`, + html`
    ${getRelativeTime(item.lastApplied)}
    + ${item.lastApplied.toLocaleString()}`, html``, html` ${msg("Update")} diff --git a/web/src/admin/tenants/TenantForm.ts b/web/src/admin/brands/BrandForm.ts similarity index 79% rename from web/src/admin/tenants/TenantForm.ts rename to web/src/admin/brands/BrandForm.ts index 51e81b24e3..114bae13e9 100644 --- a/web/src/admin/tenants/TenantForm.ts +++ b/web/src/admin/brands/BrandForm.ts @@ -8,38 +8,38 @@ import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import "@goauthentik/elements/forms/SearchSelect"; -import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand"; +import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand"; import YAML from "yaml"; import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; -import { CoreApi, FlowsInstancesListDesignationEnum, Tenant } from "@goauthentik/api"; +import { Brand, CoreApi, FlowsInstancesListDesignationEnum } from "@goauthentik/api"; -@customElement("ak-tenant-form") -export class TenantForm extends ModelForm { - loadInstance(pk: string): Promise { - return new CoreApi(DEFAULT_CONFIG).coreTenantsRetrieve({ - tenantUuid: pk, +@customElement("ak-brand-form") +export class BrandForm extends ModelForm { + loadInstance(pk: string): Promise { + return new CoreApi(DEFAULT_CONFIG).coreBrandsRetrieve({ + brandUuid: pk, }); } getSuccessMessage(): string { return this.instance - ? msg("Successfully updated tenant.") - : msg("Successfully created tenant."); + ? msg("Successfully updated brand.") + : msg("Successfully created brand."); } - async send(data: Tenant): Promise { - if (this.instance?.tenantUuid) { - return new CoreApi(DEFAULT_CONFIG).coreTenantsUpdate({ - tenantUuid: this.instance.tenantUuid, - tenantRequest: data, + async send(data: Brand): Promise { + if (this.instance?.brandUuid) { + return new CoreApi(DEFAULT_CONFIG).coreBrandsUpdate({ + brandUuid: this.instance.brandUuid, + brandRequest: data, }); } else { - return new CoreApi(DEFAULT_CONFIG).coreTenantsCreate({ - tenantRequest: data, + return new CoreApi(DEFAULT_CONFIG).coreBrandsCreate({ + brandRequest: data, }); } } @@ -77,7 +77,7 @@ export class TenantForm extends ModelForm { ${msg("Default")}

    - ${msg("Use this tenant for each domain that doesn't have a dedicated tenant.")} + ${msg("Use this brand for each domain that doesn't have a dedicated brand.")}

    @@ -93,7 +93,7 @@ export class TenantForm extends ModelForm { type="text" value="${first( this.instance?.brandingTitle, - DefaultTenant.brandingTitle, + DefaultBrand.brandingTitle, )}" class="pf-c-form-control" required @@ -109,10 +109,7 @@ export class TenantForm extends ModelForm { > @@ -129,7 +126,7 @@ export class TenantForm extends ModelForm { type="text" value="${first( this.instance?.brandingFavicon, - DefaultTenant.brandingFavicon, + DefaultBrand.brandingFavicon, )}" class="pf-c-form-control" required @@ -236,34 +233,6 @@ export class TenantForm extends ModelForm { .certificate=${this.instance?.webCertificate} > - - -

    - ${msg("Duration after which events will be deleted from the database.")} -

    -

    - ${msg( - 'When using an external logging solution for archiving, this can be set to "minutes=5".', - )} -

    -

    - ${msg( - "This setting only affects new Events, as the expiration is saved per-event.", - )} -

    -

    - ${msg('Format: "weeks=3;days=2;hours=3,seconds=2".')} -

    -
    {

    ${msg( - "Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this tenant.", + "Set custom attributes using YAML or JSON. Any attributes set here will be inherited by users, if the request is handled by this brand.", )}

    diff --git a/web/src/admin/tenants/TenantListPage.ts b/web/src/admin/brands/BrandListPage.ts similarity index 70% rename from web/src/admin/tenants/TenantListPage.ts rename to web/src/admin/brands/BrandListPage.ts index 1e4cd5d8a7..9aa0e23c23 100644 --- a/web/src/admin/tenants/TenantListPage.ts +++ b/web/src/admin/brands/BrandListPage.ts @@ -1,4 +1,4 @@ -import "@goauthentik/admin/tenants/TenantForm"; +import "@goauthentik/admin/brands/BrandForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/components/ak-status-label"; @@ -16,15 +16,15 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { CoreApi, RbacPermissionsAssignedByUsersListModelEnum, Tenant } from "@goauthentik/api"; +import { Brand, CoreApi, RbacPermissionsAssignedByUsersListModelEnum } from "@goauthentik/api"; -@customElement("ak-tenant-list") -export class TenantListPage extends TablePage { +@customElement("ak-brand-list") +export class BrandListPage extends TablePage { searchEnabled(): boolean { return true; } pageTitle(): string { - return msg("Tenants"); + return msg("Brands"); } pageDescription(): string { return msg("Configure visual settings and defaults for different domains."); @@ -34,12 +34,13 @@ export class TenantListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "domain"; - async apiEndpoint(page: number): Promise> { - return new CoreApi(DEFAULT_CONFIG).coreTenantsList({ + async apiEndpoint(page: number): Promise> { + return new CoreApi(DEFAULT_CONFIG).coreBrandsList({ ordering: this.order, page: page, pageSize: (await uiConfig()).pagination.perPage, @@ -50,6 +51,7 @@ export class TenantListPage extends TablePage { columns(): TableColumn[] { return [ new TableColumn(msg("Domain"), "domain"), + new TableColumn(msg("Brand name"), "branding_title"), new TableColumn(msg("Default?"), "default"), new TableColumn(msg("Actions")), ]; @@ -58,19 +60,19 @@ export class TenantListPage extends TablePage { renderToolbarSelected(): TemplateResult { const disabled = this.selectedElements.length < 1; return html` { + .metadata=${(item: Brand) => { return [{ key: msg("Domain"), value: item.domain }]; }} - .usedBy=${(item: Tenant) => { - return new CoreApi(DEFAULT_CONFIG).coreTenantsUsedByList({ - tenantUuid: item.tenantUuid, + .usedBy=${(item: Brand) => { + return new CoreApi(DEFAULT_CONFIG).coreBrandsUsedByList({ + brandUuid: item.brandUuid, }); }} - .delete=${(item: Tenant) => { - return new CoreApi(DEFAULT_CONFIG).coreTenantsDestroy({ - tenantUuid: item.tenantUuid, + .delete=${(item: Brand) => { + return new CoreApi(DEFAULT_CONFIG).coreBrandsDestroy({ + brandUuid: item.brandUuid, }); }} > @@ -80,14 +82,15 @@ export class TenantListPage extends TablePage { `; } - row(item: Tenant): TemplateResult[] { + row(item: Brand): TemplateResult[] { return [ html`${item.domain}`, + html`${item.brandingTitle}`, html``, html` ${msg("Update")} - ${msg("Update Tenant")} - + ${msg("Update Brand")} + `; diff --git a/web/src/admin/common/ak-core-group-search.ts b/web/src/admin/common/ak-core-group-search.ts index 768b81c519..9a54124e27 100644 --- a/web/src/admin/common/ak-core-group-search.ts +++ b/web/src/admin/common/ak-core-group-search.ts @@ -12,6 +12,7 @@ import { CoreApi, CoreGroupsListRequest, Group } from "@goauthentik/api"; async function fetchObjects(query?: string): Promise { const args: CoreGroupsListRequest = { ordering: "name", + includeUsers: false, }; if (query !== undefined) { args.search = query; @@ -57,7 +58,6 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) { super(); this.selected = this.selected.bind(this); this.handleSearchUpdate = this.handleSearchUpdate.bind(this); - this.addCustomListener("ak-change", this.handleSearchUpdate); } get value() { @@ -94,6 +94,7 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) { .renderElement=${renderElement} .value=${renderValue} .selected=${this.selected} + @ak-change=${this.handleSearchUpdate} ?blankable=${true} > diff --git a/web/src/admin/common/ak-crypto-certificate-search.ts b/web/src/admin/common/ak-crypto-certificate-search.ts index 3d913a13a8..a3a0cdab40 100644 --- a/web/src/admin/common/ak-crypto-certificate-search.ts +++ b/web/src/admin/common/ak-crypto-certificate-search.ts @@ -65,7 +65,6 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement) this.selected = this.selected.bind(this); this.fetchObjects = this.fetchObjects.bind(this); this.handleSearchUpdate = this.handleSearchUpdate.bind(this); - this.addCustomListener("ak-change", this.handleSearchUpdate); } get value() { @@ -120,6 +119,7 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement) .renderElement=${renderElement} .value=${renderValue} .selected=${this.selected} + @ak-change=${this.handleSearchUpdate} ?blankable=${true} > diff --git a/web/src/admin/common/ak-flow-search/FlowSearch.ts b/web/src/admin/common/ak-flow-search/FlowSearch.ts index 3c146a8bdd..960c0f8099 100644 --- a/web/src/admin/common/ak-flow-search/FlowSearch.ts +++ b/web/src/admin/common/ak-flow-search/FlowSearch.ts @@ -28,7 +28,7 @@ export function getFlowValue(flow: Flow | undefined): string | undefined { * * A wrapper around SearchSelect that understands the basic semantics of querying about Flows. This * code eliminates the long blocks of unreadable invocation that were embedded in every provider, as well as in - * sources, tenants, and applications. + * sources, brands, and applications. * */ @@ -46,8 +46,8 @@ export class FlowSearch extends CustomListenerElement(AKElement) * * @attr */ - @property({ attribute: false }) - currentFlow: string | undefined; + @property({ type: String }) + currentFlow?: string | undefined; /** * If true, it is not valid to leave the flow blank. @@ -74,7 +74,6 @@ export class FlowSearch extends CustomListenerElement(AKElement) this.fetchObjects = this.fetchObjects.bind(this); this.selected = this.selected.bind(this); this.handleSearchUpdate = this.handleSearchUpdate.bind(this); - this.addCustomListener("ak-change", this.handleSearchUpdate); } handleSearchUpdate(ev: CustomEvent) { @@ -94,7 +93,7 @@ export class FlowSearch extends CustomListenerElement(AKElement) } /* This is the most commonly overridden method of this class. About half of the Flow Searches - * use this method, but several have more complex needs, such as relating to the tenant, or just + * use this method, but several have more complex needs, such as relating to the brand, or just * returning false. */ @@ -124,6 +123,7 @@ export class FlowSearch extends CustomListenerElement(AKElement) .renderDescription=${renderDescription} .value=${getFlowValue} .name=${this.name} + @ak-change=${this.handleSearchUpdate} ?blankable=${!this.required} > diff --git a/web/src/admin/common/ak-flow-search/ak-branded-flow-search.ts b/web/src/admin/common/ak-flow-search/ak-branded-flow-search.ts new file mode 100644 index 0000000000..89a104cf1e --- /dev/null +++ b/web/src/admin/common/ak-flow-search/ak-branded-flow-search.ts @@ -0,0 +1,34 @@ +import { customElement, property } from "lit/decorators.js"; + +import type { Flow } from "@goauthentik/api"; + +import FlowSearch from "./FlowSearch"; + +/** + * Search for flows that may have a fallback specified by the brand settings + * + * @element ak-branded-flow-search + * + */ + +@customElement("ak-branded-flow-search") +export class AkBrandedFlowSearch extends FlowSearch { + /** + * The Associated ID of the flow the brand has, to compare if possible + * + * @attr + */ + @property({ attribute: false, type: String }) + brandFlow?: string; + + constructor() { + super(); + this.selected = this.selected.bind(this); + } + + selected(flow: Flow): boolean { + return super.selected(flow) || flow.pk === this.brandFlow; + } +} + +export default AkBrandedFlowSearch; diff --git a/web/src/admin/common/ak-flow-search/ak-flow-search-no-default.ts b/web/src/admin/common/ak-flow-search/ak-flow-search-no-default.ts index 4942d1590c..0b716392f8 100644 --- a/web/src/admin/common/ak-flow-search/ak-flow-search-no-default.ts +++ b/web/src/admin/common/ak-flow-search/ak-flow-search-no-default.ts @@ -24,6 +24,7 @@ export class AkFlowSearchNoDefault extends FlowSearch { .renderElement=${renderElement} .renderDescription=${renderDescription} .value=${getFlowValue} + @ak-change=${this.handleSearchUpdate} ?blankable=${!this.required} > diff --git a/web/src/admin/common/ak-flow-search/ak-flow-search.stories.ts b/web/src/admin/common/ak-flow-search/ak-flow-search.stories.ts new file mode 100644 index 0000000000..698b0cb42b --- /dev/null +++ b/web/src/admin/common/ak-flow-search/ak-flow-search.stories.ts @@ -0,0 +1,136 @@ +import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; +import { AkFlowSearch } from "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { Meta } from "@storybook/web-components"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; + +import { Flow, FlowsInstancesListDesignationEnum } from "@goauthentik/api"; + +const mockData = { + pagination: { + next: 0, + previous: 0, + count: 2, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 2, + }, + results: [ + { + pk: "41468774-bef6-4ffb-b675-332d0d8c5d25", + policybindingmodel_ptr_id: "0fb5b872-2734-44bd-ac7e-f23051481a83", + name: "Authorize Application", + slug: "default-provider-authorization-explicit-consent", + title: "Redirecting to %(app)s", + designation: "authorization", + background: "/static/dist/assets/images/flow_background.jpg", + stages: ["8adcdc74-0d3d-48a8-b628-38e3da4081e5"], + policies: [], + cache_count: 0, + policy_engine_mode: "any", + compatibility_mode: false, + export_url: + "/api/v3/flows/instances/default-provider-authorization-explicit-consent/export/", + layout: "stacked", + denied_action: "message_continue", + authentication: "require_authenticated", + }, + { + pk: "89f57fd8-fd1e-42be-a5fd-abc13b19529b", + policybindingmodel_ptr_id: "e8526408-c6ee-46e1-bbfe-a1d37c2c02c8", + name: "Authorize Application", + slug: "default-provider-authorization-implicit-consent", + title: "Redirecting to %(app)s", + designation: "authorization", + background: "/static/dist/assets/images/flow_background.jpg", + stages: [], + policies: [], + cache_count: 0, + policy_engine_mode: "any", + compatibility_mode: false, + export_url: + "/api/v3/flows/instances/default-provider-authorization-implicit-consent/export/", + layout: "stacked", + denied_action: "message_continue", + authentication: "require_authenticated", + }, + ], +}; + +const metadata: Meta> = { + title: "Elements / Select Search / Flow", + component: "ak-flow-search", + parameters: { + docs: { + description: { + component: "A Select Search for Authentication Flows", + }, + }, + mockData: [ + { + url: `${window.location.origin}/api/v3/flows/instances/?designation=authorization&ordering=slug`, + method: "GET", + status: 200, + response: () => mockData, + }, + ], + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => { + return html`
    + + ${testItem} +
      +
      `; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const displayChange = (ev: any) => { + document.getElementById("message-pad")!.innerText = `Value selected: ${JSON.stringify( + ev.target.value, + null, + 2, + )}`; +}; + +export const Default = () => + container( + html` + `, + ); + +export const WithInitialValue = () => + container( + html` + `, + ); diff --git a/web/src/admin/common/ak-flow-search/ak-tenanted-flow-search.ts b/web/src/admin/common/ak-flow-search/ak-tenanted-flow-search.ts deleted file mode 100644 index 6c89e1baaf..0000000000 --- a/web/src/admin/common/ak-flow-search/ak-tenanted-flow-search.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { customElement, property } from "lit/decorators.js"; - -import type { Flow } from "@goauthentik/api"; - -import FlowSearch from "./FlowSearch"; - -/** - * Search for flows that may have a fallback specified by the tenant settings - * - * @element ak-tenanted-flow-search - * - */ - -@customElement("ak-tenanted-flow-search") -export class AkTenantedFlowSearch extends FlowSearch { - /** - * The Associated ID of the flow the tenant has, to compare if possible - * - * @attr - */ - @property({ attribute: false, type: String }) - tenantFlow?: string; - - constructor() { - super(); - this.selected = this.selected.bind(this); - } - - selected(flow: Flow): boolean { - return super.selected(flow) || flow.pk === this.tenantFlow; - } -} - -export default AkTenantedFlowSearch; diff --git a/web/src/admin/common/ak-license-notice.ts b/web/src/admin/common/ak-license-notice.ts new file mode 100644 index 0000000000..4cc978ac4f --- /dev/null +++ b/web/src/admin/common/ak-license-notice.ts @@ -0,0 +1,23 @@ +import "@goauthentik/elements/Alert"; +import { AKElement } from "@goauthentik/elements/Base"; +import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; + +import { msg } from "@lit/localize"; +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +@customElement("ak-license-notice") +export class AkLicenceNotice extends WithLicenseSummary(AKElement) { + @property() + notice = msg("Enterprise only"); + + render() { + return this.hasEnterpriseLicense + ? nothing + : html` + + ${this.notice} + + `; + } +} diff --git a/web/src/admin/crypto/CertificateGenerateForm.ts b/web/src/admin/crypto/CertificateGenerateForm.ts index 85f2fde9ec..ada0625e76 100644 --- a/web/src/admin/crypto/CertificateGenerateForm.ts +++ b/web/src/admin/crypto/CertificateGenerateForm.ts @@ -6,7 +6,12 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; -import { CertificateGenerationRequest, CertificateKeyPair, CryptoApi } from "@goauthentik/api"; +import { + AlgEnum, + CertificateGenerationRequest, + CertificateKeyPair, + CryptoApi, +} from "@goauthentik/api"; @customElement("ak-crypto-certificate-generate-form") export class CertificateKeyPairForm extends Form { @@ -40,6 +45,29 @@ export class CertificateKeyPairForm extends Form { ?required=${true} > - `; + + + + +

      + ${msg("Algorithm used to generate the private key.")} +

      +
      `; } } diff --git a/web/src/admin/crypto/CertificateKeyPairListPage.ts b/web/src/admin/crypto/CertificateKeyPairListPage.ts index c76feff3a5..fd71112273 100644 --- a/web/src/admin/crypto/CertificateKeyPairListPage.ts +++ b/web/src/admin/crypto/CertificateKeyPairListPage.ts @@ -29,6 +29,7 @@ import { export class CertificateKeyPairListPage extends TablePage { expandable = true; checkbox = true; + clearOnRefresh = true; searchEnabled(): boolean { return true; diff --git a/web/src/admin/enterprise/EnterpriseLicenseForm.ts b/web/src/admin/enterprise/EnterpriseLicenseForm.ts index ebce9938db..025df416b1 100644 --- a/web/src/admin/enterprise/EnterpriseLicenseForm.ts +++ b/web/src/admin/enterprise/EnterpriseLicenseForm.ts @@ -1,4 +1,5 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants"; import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; @@ -34,14 +35,19 @@ export class EnterpriseLicenseForm extends ModelForm { } async send(data: License): Promise { - return this.instance - ? new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicensePartialUpdate({ - licenseUuid: this.instance.licenseUuid || "", - patchedLicenseRequest: data, - }) - : new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseCreate({ - licenseRequest: data, - }); + return ( + this.instance + ? new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicensePartialUpdate({ + licenseUuid: this.instance.licenseUuid || "", + patchedLicenseRequest: data, + }) + : new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseCreate({ + licenseRequest: data, + }) + ).then((data) => { + window.dispatchEvent(new CustomEvent(EVENT_REFRESH_ENTERPRISE)); + return data; + }); } renderForm(): TemplateResult { diff --git a/web/src/admin/enterprise/EnterpriseLicenseListPage.ts b/web/src/admin/enterprise/EnterpriseLicenseListPage.ts index 0d2f16cf3f..324ebca257 100644 --- a/web/src/admin/enterprise/EnterpriseLicenseListPage.ts +++ b/web/src/admin/enterprise/EnterpriseLicenseListPage.ts @@ -1,6 +1,7 @@ import "@goauthentik/admin/enterprise/EnterpriseLicenseForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/Spinner"; import "@goauthentik/elements/buttons/SpinnerButton"; @@ -35,6 +36,7 @@ import { @customElement("ak-enterprise-license-list") export class EnterpriseLicenseListPage extends TablePage { checkbox = true; + clearOnRefresh = true; searchEnabled(): boolean { return true; @@ -73,6 +75,9 @@ export class EnterpriseLicenseListPage extends TablePage { .pf-m-no-padding-bottom { padding-bottom: 0; } + .install-id { + word-break: break-all; + } `, ); } @@ -148,30 +153,11 @@ export class EnterpriseLicenseListPage extends TablePage { renderSectionBefore(): TemplateResult { return html` -
      - ${msg("Enterprise is in preview.")} - ${msg("Send us feedback!")} -
      -
      -
      ${msg("Get a license")}
      -
      - ${this.installID - ? html` ${msg("Go to Customer Portal")}` - : html``} -
      -
      - + ${this.renderGetLicenseCard()} { subtext=${msg("Cumulative license expiry")} > ${this.summary?.hasLicense - ? this.summary.latestValid.toLocaleString() + ? html`
      ${getRelativeTime(this.summary.latestValid)}
      + ${this.summary.latestValid.toLocaleString()}` : "-"}
      @@ -246,6 +233,43 @@ export class EnterpriseLicenseListPage extends TablePage { ]; } + renderGetLicenseCard() { + const renderSpinner = () => + html`
      + +
      `; + + const installURL = (installID: string) => + [ + "https://customers.goauthentik.io/from_authentik/purchase/?install_id=", + encodeURIComponent(installID), + "&authentik_url=", + encodeURI(window.location.origin), + ].join(""); + + const renderCard = (installID: string) => html` +
      ${msg("Your Install ID")}
      +
      ${installID}
      + + + `; + + return html`
      + ${this.installID ? renderCard(this.installID) : renderSpinner()} +
      `; + } + renderObjectCreate(): TemplateResult { return html` diff --git a/web/src/admin/events/EventListPage.ts b/web/src/admin/events/EventListPage.ts index 7ba75da8f8..e4e1c7efc4 100644 --- a/web/src/admin/events/EventListPage.ts +++ b/web/src/admin/events/EventListPage.ts @@ -1,16 +1,17 @@ import "@goauthentik/admin/events/EventVolumeChart"; -import { EventGeo } from "@goauthentik/admin/events/utils"; +import { EventGeo, EventUser } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { actionToLabel } from "@goauthentik/common/labels"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-event-info"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; -import { msg, str } from "@lit/localize"; +import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; @@ -59,7 +60,7 @@ export class EventListPage extends TablePage { new TableColumn(msg("User"), "user"), new TableColumn(msg("Creation Date"), "created"), new TableColumn(msg("Client IP"), "client_ip"), - new TableColumn(msg("Tenant"), "tenant_name"), + new TableColumn(msg("Brand"), "brand_name"), new TableColumn(msg("Actions")), ]; } @@ -81,23 +82,12 @@ export class EventListPage extends TablePage { return [ html`
      ${actionToLabel(item.action)}
      ${item.app}`, - item.user?.username - ? html` - ${item.user.on_behalf_of - ? html` - ${msg(str`On behalf of ${item.user.on_behalf_of.username}`)} - ` - : html``}` - : html`-`, - html`${item.created?.toLocaleString()}`, + EventUser(item), + html`
      ${getRelativeTime(item.created)}
      + ${item.created.toLocaleString()}`, html`
      ${item.clientIp || msg("-")}
      - ${EventGeo(item)}`, - html`${item.tenant?.name || msg("-")}`, + html`${item.brand?.name || msg("-")}`, html` @@ -107,7 +97,7 @@ export class EventListPage extends TablePage { } renderExpanded(item: Event): TemplateResult { - return html` + return html`
      diff --git a/web/src/admin/events/EventViewPage.ts b/web/src/admin/events/EventViewPage.ts index b5351840ae..ddf29fbfbb 100644 --- a/web/src/admin/events/EventViewPage.ts +++ b/web/src/admin/events/EventViewPage.ts @@ -1,14 +1,15 @@ -import { EventGeo } from "@goauthentik/admin/events/utils"; +import { EventGeo, EventUser } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { actionToLabel } from "@goauthentik/common/labels"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-event-info"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/PageHeader"; import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; @@ -17,28 +18,32 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { EventsApi } from "@goauthentik/api"; +import { EventToJSON, EventsApi } from "@goauthentik/api"; @customElement("ak-event-view") export class EventViewPage extends AKElement { - @property() - set eventID(value: string) { - new EventsApi(DEFAULT_CONFIG) - .eventsEventsRetrieve({ - eventUuid: value, - }) - .then((ev) => { - this.event = ev as EventWithContext; - }); - } + @property({ type: String }) + eventID?: string; - @property({ attribute: false }) + @state() event!: EventWithContext; static get styles(): CSSResult[] { return [PFBase, PFGrid, PFDescriptionList, PFPage, PFContent, PFCard]; } + fetchEvent(eventUuid: string) { + new EventsApi(DEFAULT_CONFIG).eventsEventsRetrieve({ eventUuid }).then((ev) => { + this.event = ev as EventWithContext; + }); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("eventID") && this.eventID) { + this.fetchEvent(this.eventID); + } + } + render(): TemplateResult { if (!this.event) { return html` @@ -87,27 +92,7 @@ export class EventViewPage extends AKElement {
      - ${this.event.user?.username - ? html` - ${this.event.user.on_behalf_of - ? html` - ${msg( - str`On behalf of ${this.event.user.on_behalf_of.username}`, - )} - ` - : html``}` - : html`-`} + ${EventUser(this.event)}
      @@ -119,7 +104,8 @@ export class EventViewPage extends AKElement {
      - ${this.event.created?.toLocaleString()} +
      ${getRelativeTime(this.event.created)}
      + ${this.event.created.toLocaleString()}
      @@ -139,12 +125,12 @@ export class EventViewPage extends AKElement {
      ${msg("Tenant")}${msg("Brand")}
      - ${this.event.tenant?.name || msg("-")} + ${this.event.brand?.name || msg("-")}
      @@ -154,6 +140,12 @@ export class EventViewPage extends AKElement {
      +
      +
      ${msg("Raw event info")}
      +
      +
      ${JSON.stringify(EventToJSON(this.event), null, 4)}
      +
      +
      `; } diff --git a/web/src/admin/events/EventVolumeChart.ts b/web/src/admin/events/EventVolumeChart.ts index da1710af02..623aa03456 100644 --- a/web/src/admin/events/EventVolumeChart.ts +++ b/web/src/admin/events/EventVolumeChart.ts @@ -1,5 +1,5 @@ -import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; -import { AKChart } from "@goauthentik/app/elements/charts/Chart"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { AKChart } from "@goauthentik/elements/charts/Chart"; import { ChartData } from "chart.js"; import { msg } from "@lit/localize"; diff --git a/web/src/admin/events/RuleForm.ts b/web/src/admin/events/RuleForm.ts index ed392a1ca3..7ee190047a 100644 --- a/web/src/admin/events/RuleForm.ts +++ b/web/src/admin/events/RuleForm.ts @@ -69,6 +69,7 @@ export class RuleForm extends ModelForm { .fetchObjects=${async (query?: string): Promise => { const args: CoreGroupsListRequest = { ordering: "name", + includeUsers: false, }; if (query !== undefined) { args.search = query; diff --git a/web/src/admin/events/RuleListPage.ts b/web/src/admin/events/RuleListPage.ts index e997903b17..7afe34104e 100644 --- a/web/src/admin/events/RuleListPage.ts +++ b/web/src/admin/events/RuleListPage.ts @@ -27,6 +27,7 @@ import { export class RuleListPage extends TablePage { expandable = true; checkbox = true; + clearOnRefresh = true; searchEnabled(): boolean { return true; diff --git a/web/src/admin/events/TransportListPage.ts b/web/src/admin/events/TransportListPage.ts index c36c21af99..ffa4f6263d 100644 --- a/web/src/admin/events/TransportListPage.ts +++ b/web/src/admin/events/TransportListPage.ts @@ -38,6 +38,7 @@ export class TransportListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "name"; diff --git a/web/src/admin/events/utils.ts b/web/src/admin/events/utils.ts index d6aac8e3ad..89999d39d6 100644 --- a/web/src/admin/events/utils.ts +++ b/web/src/admin/events/utils.ts @@ -1,6 +1,8 @@ import { EventWithContext } from "@goauthentik/common/events"; +import { truncate } from "@goauthentik/common/utils"; import { KeyUnknown } from "@goauthentik/elements/forms/Form"; +import { msg, str } from "@lit/localize"; import { TemplateResult, html } from "lit"; export function EventGeo(event: EventWithContext): TemplateResult { @@ -14,3 +16,29 @@ export function EventGeo(event: EventWithContext): TemplateResult { } return html``; } + +export function EventUser(event: EventWithContext, truncateUsername?: number): TemplateResult { + if (!event.user.username) { + return html`-`; + } + let body = html``; + if (event.user.is_anonymous) { + body = html`
      ${msg("Anonymous user")}
      `; + } else { + body = html``; + } + if (event.user.on_behalf_of) { + body = html`${body} + ${msg(str`On behalf of ${event.user.on_behalf_of.username}`)} + `; + } + return body; +} diff --git a/web/src/admin/flows/BoundStagesList.ts b/web/src/admin/flows/BoundStagesList.ts index 65eb7ec56a..316d4ca577 100644 --- a/web/src/admin/flows/BoundStagesList.ts +++ b/web/src/admin/flows/BoundStagesList.ts @@ -21,6 +21,9 @@ import { FlowStageBinding, FlowsApi } from "@goauthentik/api"; export class BoundStagesList extends Table { expandable = true; checkbox = true; + clearOnRefresh = true; + + order = "order"; @property() target?: string; @@ -28,7 +31,7 @@ export class BoundStagesList extends Table { async apiEndpoint(page: number): Promise> { return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({ target: this.target || "", - ordering: "order", + ordering: this.order, page: page, pageSize: (await uiConfig()).pagination.perPage, }); @@ -36,8 +39,8 @@ export class BoundStagesList extends Table { columns(): TableColumn[] { return [ - new TableColumn(msg("Order")), - new TableColumn(msg("Name")), + new TableColumn(msg("Order"), "order"), + new TableColumn(msg("Name"), "stage__name"), new TableColumn(msg("Type")), new TableColumn(msg("Actions")), ]; diff --git a/web/src/admin/flows/FlowForm.ts b/web/src/admin/flows/FlowForm.ts index eb37aa4ca5..3925f6db95 100644 --- a/web/src/admin/flows/FlowForm.ts +++ b/web/src/admin/flows/FlowForm.ts @@ -1,8 +1,11 @@ import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/utils"; import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum"; -import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; @@ -14,17 +17,16 @@ import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { - CapabilitiesEnum, DeniedActionEnum, Flow, FlowDesignationEnum, + FlowLayoutEnum, FlowsApi, - LayoutEnum, PolicyEngineMode, } from "@goauthentik/api"; @customElement("ak-flow-form") -export class FlowForm extends ModelForm { +export class FlowForm extends WithCapabilitiesConfig(ModelForm) { async loadInstance(pk: string): Promise { const flow = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesRetrieve({ slug: pk, @@ -54,8 +56,8 @@ export class FlowForm extends ModelForm { flowRequest: data, }); } - const c = await config(); - if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) { + + if (this.can(CapabilitiesEnum.CanSaveMedia)) { const icon = this.getFormFiles()["background"]; if (icon || this.clearBackground) { await new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundCreate({ @@ -196,6 +198,13 @@ export class FlowForm extends ModelForm { > ${msg("Require superuser.")} +

      ${msg("Required authentication level for this flow.")} @@ -302,38 +311,38 @@ export class FlowForm extends ModelForm { > - ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` {

      - ${(this.result?.logs || []).length > 0 - ? this.result?.logs?.map((m) => { - return html`
      -
      - ${m.log_level} -
      -
      -
      - ${m.event} -
      -
      -
      `; - }) - : html`
      -
      - ${msg("No log messages.")} -
      -
      `} +
      diff --git a/web/src/admin/flows/FlowListPage.ts b/web/src/admin/flows/FlowListPage.ts index 5bcc6205d4..1e264dc92e 100644 --- a/web/src/admin/flows/FlowListPage.ts +++ b/web/src/admin/flows/FlowListPage.ts @@ -37,6 +37,7 @@ export class FlowListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "slug"; diff --git a/web/src/admin/flows/FlowViewPage.ts b/web/src/admin/flows/FlowViewPage.ts index c760821e50..e7c42c1d6c 100644 --- a/web/src/admin/flows/FlowViewPage.ts +++ b/web/src/admin/flows/FlowViewPage.ts @@ -1,19 +1,19 @@ import "@goauthentik/admin/flows/BoundStagesList"; import "@goauthentik/admin/flows/FlowDiagram"; import "@goauthentik/admin/flows/FlowForm"; +import { DesignationToLabel } from "@goauthentik/admin/flows/utils"; import "@goauthentik/admin/policies/BoundPoliciesList"; -import { DesignationToLabel } from "@goauthentik/app/admin/flows/utils"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/components/events/ObjectChangelog"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { CSSResult, PropertyValues, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; @@ -32,18 +32,10 @@ import { @customElement("ak-flow-view") export class FlowViewPage extends AKElement { - @property() - set flowSlug(value: string) { - new FlowsApi(DEFAULT_CONFIG) - .flowsInstancesRetrieve({ - slug: value, - }) - .then((flow) => { - this.flow = flow; - }); - } + @property({ type: String }) + flowSlug?: string; - @property({ attribute: false }) + @state() flow!: Flow; static get styles(): CSSResult[] { @@ -57,6 +49,18 @@ export class FlowViewPage extends AKElement { `); } + fetchFlow(slug: string) { + new FlowsApi(DEFAULT_CONFIG).flowsInstancesRetrieve({ slug }).then((flow) => { + this.flow = flow; + }); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("flowSlug") && this.flowSlug) { + this.fetchFlow(this.flowSlug); + } + } + render(): TemplateResult { if (!this.flow) { return html``; diff --git a/web/src/admin/flows/utils.ts b/web/src/admin/flows/utils.ts index 3c5ae8f297..90c123c9ad 100644 --- a/web/src/admin/flows/utils.ts +++ b/web/src/admin/flows/utils.ts @@ -1,6 +1,6 @@ import { msg } from "@lit/localize"; -import { Flow, FlowDesignationEnum, LayoutEnum } from "@goauthentik/api"; +import { Flow, FlowDesignationEnum, FlowLayoutEnum } from "@goauthentik/api"; export function RenderFlowOption(flow: Flow): string { return `${flow.slug} (${flow.name})`; @@ -27,19 +27,19 @@ export function DesignationToLabel(designation: FlowDesignationEnum): string { } } -export function LayoutToLabel(layout: LayoutEnum): string { +export function LayoutToLabel(layout: FlowLayoutEnum): string { switch (layout) { - case LayoutEnum.Stacked: + case FlowLayoutEnum.Stacked: return msg("Stacked"); - case LayoutEnum.ContentLeft: + case FlowLayoutEnum.ContentLeft: return msg("Content left"); - case LayoutEnum.ContentRight: + case FlowLayoutEnum.ContentRight: return msg("Content right"); - case LayoutEnum.SidebarLeft: + case FlowLayoutEnum.SidebarLeft: return msg("Sidebar left"); - case LayoutEnum.SidebarRight: + case FlowLayoutEnum.SidebarRight: return msg("Sidebar right"); - case LayoutEnum.UnknownDefaultOpenApi: + case FlowLayoutEnum.UnknownDefaultOpenApi: return msg("Unknown layout"); } } diff --git a/web/src/admin/groups/GroupForm.ts b/web/src/admin/groups/GroupForm.ts index b55cafb566..c8be5f65bd 100644 --- a/web/src/admin/groups/GroupForm.ts +++ b/web/src/admin/groups/GroupForm.ts @@ -3,6 +3,8 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; import "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider"; +import { DataProvision, DualSelectPair } from "@goauthentik/elements/ak-dual-select/types"; import "@goauthentik/elements/chips/Chip"; import "@goauthentik/elements/chips/ChipGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; @@ -12,22 +14,17 @@ import YAML from "yaml"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement, state } from "lit/decorators.js"; +import { customElement } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { - CoreApi, - CoreGroupsListRequest, - Group, - PaginatedRoleList, - RbacApi, -} from "@goauthentik/api"; +import { CoreApi, CoreGroupsListRequest, Group, RbacApi, Role } from "@goauthentik/api"; + +export function rbacRolePair(item: Role): DualSelectPair { + return [item.pk, html`
      ${item.name}
      `, item.name]; +} @customElement("ak-group-form") export class GroupForm extends ModelForm { - @state() - roles?: PaginatedRoleList; - static get styles(): CSSResult[] { return super.styles.concat(css` .pf-c-button.pf-m-control { @@ -42,6 +39,7 @@ export class GroupForm extends ModelForm { loadInstance(pk: string): Promise { return new CoreApi(DEFAULT_CONFIG).coreGroupsRetrieve({ groupUuid: pk, + includeUsers: false, }); } @@ -51,12 +49,6 @@ export class GroupForm extends ModelForm { : msg("Successfully created group."); } - async load(): Promise { - this.roles = await new RbacApi(DEFAULT_CONFIG).rbacRolesList({ - ordering: "name", - }); - } - async send(data: Group): Promise { if (this.instance?.pk) { return new CoreApi(DEFAULT_CONFIG).coreGroupsPartialUpdate({ @@ -127,24 +119,29 @@ export class GroupForm extends ModelForm { - + => { + return new RbacApi(DEFAULT_CONFIG) + .rbacRolesList({ + page: page, + search: search, + }) + .then((results) => { + return { + pagination: results.pagination, + options: results.results.map(rbacRolePair), + }; + }); + }} + .selected=${(this.instance?.rolesObj ?? []).map(rbacRolePair)} + available-label="${msg("Available Roles")}" + selected-label="${msg("Selected Roles")}" + >

      ${msg( "Select roles to grant this groups' users' permissions from the selected roles.", )}

      -

      - ${msg("Hold control/command to select multiple items.")} -

      { checkbox = true; + clearOnRefresh = true; searchEnabled(): boolean { return true; } @@ -41,6 +42,7 @@ export class GroupListPage extends TablePage { page: page, pageSize: (await uiConfig()).pagination.perPage, search: this.search || "", + includeUsers: false, }); } diff --git a/web/src/admin/groups/GroupViewPage.ts b/web/src/admin/groups/GroupViewPage.ts index 8faaca1988..438d083be2 100644 --- a/web/src/admin/groups/GroupViewPage.ts +++ b/web/src/admin/groups/GroupViewPage.ts @@ -1,6 +1,5 @@ import "@goauthentik/admin/groups/GroupForm"; -import "@goauthentik/app/admin/groups/RelatedUserList"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; +import "@goauthentik/admin/groups/RelatedUserList"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/ak-status-label"; @@ -12,6 +11,7 @@ import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; @@ -37,6 +37,7 @@ export class GroupViewPage extends AKElement { new CoreApi(DEFAULT_CONFIG) .coreGroupsRetrieve({ groupUuid: id, + includeUsers: false, }) .then((group) => { this.group = group; @@ -117,7 +118,7 @@ export class GroupViewPage extends AKElement {
      @@ -199,7 +200,6 @@ export class GroupViewPage extends AKElement { - { page: page, pageSize: (await uiConfig()).pagination.perPage, search: this.search || "", + includeGroups: false, }); } @@ -49,7 +50,10 @@ export class MemberSelectTable extends TableModal { html`
      ${item.username}
      ${item.name}`, html` `, - html`${first(item.lastLogin?.toLocaleString(), msg("-"))}`, + html`${item.lastLogin + ? html`
      ${getRelativeTime(item.lastLogin)}
      + ${item.lastLogin.toLocaleString()}` + : msg("-")}`, ]; } diff --git a/web/src/admin/groups/RelatedGroupList.ts b/web/src/admin/groups/RelatedGroupList.ts index 19adf12767..e1c253fd1a 100644 --- a/web/src/admin/groups/RelatedGroupList.ts +++ b/web/src/admin/groups/RelatedGroupList.ts @@ -87,6 +87,7 @@ export class RelatedGroupAdd extends Form<{ groups: string[] }> { @customElement("ak-group-related-list") export class RelatedGroupList extends Table { checkbox = true; + clearOnRefresh = true; searchEnabled(): boolean { return true; } @@ -104,6 +105,7 @@ export class RelatedGroupList extends Table { pageSize: (await uiConfig()).pagination.perPage, search: this.search || "", membersByPk: this.targetUser ? [this.targetUser.pk] : [], + includeUsers: false, }); } @@ -124,6 +126,7 @@ export class RelatedGroupList extends Table { actionSubtext=${msg( str`Are you sure you want to remove user ${this.targetUser?.username} from the following groups?`, )} + buttonLabel=${msg("Remove")} .objects=${this.selectedElements} .delete=${(item: Group) => { if (!this.targetUser) return; diff --git a/web/src/admin/groups/RelatedUserList.ts b/web/src/admin/groups/RelatedUserList.ts index 2474ee2fec..fd502e6736 100644 --- a/web/src/admin/groups/RelatedUserList.ts +++ b/web/src/admin/groups/RelatedUserList.ts @@ -3,13 +3,17 @@ import "@goauthentik/admin/users/UserActiveForm"; import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserPasswordForm"; import "@goauthentik/admin/users/UserResetEmailForm"; -import { me } from "@goauthentik/app/common/users"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { MessageLevel } from "@goauthentik/common/messages"; import { uiConfig } from "@goauthentik/common/ui/config"; -import { first } from "@goauthentik/common/utils"; +import { me } from "@goauthentik/common/users"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/forms/DeleteBulkForm"; @@ -33,7 +37,6 @@ import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import { - CapabilitiesEnum, CoreApi, CoreUsersListTypeEnum, Group, @@ -107,9 +110,10 @@ export class RelatedUserAdd extends Form<{ users: number[] }> { } @customElement("ak-user-related-list") -export class RelatedUserList extends Table { +export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Table)) { expandable = true; checkbox = true; + clearOnRefresh = true; searchEnabled(): boolean { return true; @@ -141,6 +145,7 @@ export class RelatedUserList extends Table { type: this.hideServiceAccounts ? [CoreUsersListTypeEnum.External, CoreUsersListTypeEnum.Internal] : undefined, + includeGroups: false, }); this.me = await me(); return users; @@ -160,6 +165,7 @@ export class RelatedUserList extends Table { return html` { row(item: User): TemplateResult[] { const canImpersonate = - rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && - item.pk !== this.me?.user.pk; + this.can(CapabilitiesEnum.CanImpersonate) && item.pk !== this.me?.user.pk; return [ html`
      ${item.username}
      ${item.name}
      `, html``, - html`${first(item.lastLogin?.toLocaleString(), msg("-"))}`, + html`${item.lastLogin + ? html`
      ${getRelativeTime(item.lastLogin)}
      + ${item.lastLogin.toLocaleString()}` + : msg("-")}`, html` ${msg("Update")} ${msg("Update User")} @@ -293,13 +301,13 @@ export class RelatedUserList extends Table { ${msg("Set password")} - ${rootInterface()?.tenant?.flowRecovery + ${this.brand?.flowRecovery ? html` { return new CoreApi(DEFAULT_CONFIG) - .coreUsersRecoveryRetrieve({ + .coreUsersRecoveryCreate({ id: item.pk, }) .then((rec) => { @@ -355,7 +363,7 @@ export class RelatedUserList extends Table { ` : html`

      ${msg( - "To let a user directly reset a their password, configure a recovery flow on the currently active tenant.", + "To let a user directly reset a their password, configure a recovery flow on the currently active brand.", )}

      `} diff --git a/web/src/admin/outposts/OutpostForm.ts b/web/src/admin/outposts/OutpostForm.ts index 1952cfe859..d5791caded 100644 --- a/web/src/admin/outposts/OutpostForm.ts +++ b/web/src/admin/outposts/OutpostForm.ts @@ -3,15 +3,20 @@ import { docLink } from "@goauthentik/common/global"; import { groupBy } from "@goauthentik/common/utils"; import "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider"; +import { DataProvider, DualSelectPair } from "@goauthentik/elements/ak-dual-select/types"; +import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import "@goauthentik/elements/forms/SearchSelect"; +import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import YAML from "yaml"; import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; +import { map } from "lit/directives/map.js"; import { Outpost, @@ -19,13 +24,70 @@ import { OutpostTypeEnum, OutpostsApi, OutpostsServiceConnectionsAllListRequest, - PaginatedLDAPProviderList, - PaginatedProxyProviderList, - PaginatedRadiusProviderList, ProvidersApi, ServiceConnection, } from "@goauthentik/api"; +interface ProviderBase { + pk: number; + name: string; + assignedBackchannelApplicationName?: string; + assignedApplicationName?: string; +} + +const api = () => new ProvidersApi(DEFAULT_CONFIG); +const providerListArgs = (page: number, search = "") => ({ + ordering: "name", + applicationIsnull: false, + pageSize: 20, + search: search.trim(), + page, +}); + +const dualSelectPairMaker = (item: ProviderBase): DualSelectPair => { + const label = item.assignedBackchannelApplicationName + ? item.assignedBackchannelApplicationName + : item.assignedApplicationName; + return [ + `${item.pk}`, + html`
      ${label}
      +
      ${item.name}
      `, + label, + ]; +}; + +const provisionMaker = (results: PaginatedResponse) => ({ + pagination: results.pagination, + options: results.results.map(dualSelectPairMaker), +}); + +const proxyListFetch = async (page: number, search = "") => + provisionMaker(await api().providersProxyList(providerListArgs(page, search))); + +const ldapListFetch = async (page: number, search = "") => + provisionMaker(await api().providersLdapList(providerListArgs(page, search))); + +const radiusListFetch = async (page: number, search = "") => + provisionMaker(await api().providersRadiusList(providerListArgs(page, search))); + +const racListProvider = async (page: number, search = "") => + provisionMaker(await api().providersRacList(providerListArgs(page, search))); + +function providerProvider(type: OutpostTypeEnum): DataProvider { + switch (type) { + case OutpostTypeEnum.Proxy: + return proxyListFetch; + case OutpostTypeEnum.Ldap: + return ldapListFetch; + case OutpostTypeEnum.Radius: + return radiusListFetch; + case OutpostTypeEnum.Rac: + return racListProvider; + default: + throw new Error(`Unrecognized OutputType: ${type}`); + } +} + @customElement("ak-outpost-form") export class OutpostForm extends ModelForm { @property() @@ -35,11 +97,7 @@ export class OutpostForm extends ModelForm { embedded = false; @state() - providers?: - | PaginatedProxyProviderList - | PaginatedLDAPProviderList - | PaginatedRadiusProviderList; - + providers?: DataProvider; defaultConfig?: OutpostDefaultConfig; async loadInstance(pk: string): Promise { @@ -54,28 +112,7 @@ export class OutpostForm extends ModelForm { this.defaultConfig = await new OutpostsApi( DEFAULT_CONFIG, ).outpostsInstancesDefaultSettingsRetrieve(); - switch (this.type) { - case OutpostTypeEnum.Proxy: - this.providers = await new ProvidersApi(DEFAULT_CONFIG).providersProxyList({ - ordering: "name", - applicationIsnull: false, - }); - break; - case OutpostTypeEnum.Ldap: - this.providers = await new ProvidersApi(DEFAULT_CONFIG).providersLdapList({ - ordering: "name", - applicationIsnull: false, - }); - break; - case OutpostTypeEnum.Radius: - this.providers = await new ProvidersApi(DEFAULT_CONFIG).providersRadiusList({ - ordering: "name", - applicationIsnull: false, - }); - break; - case OutpostTypeEnum.UnknownDefaultOpenApi: - this.providers = undefined; - } + this.providers = providerProvider(this.type); } getSuccessMessage(): string { @@ -98,6 +135,13 @@ export class OutpostForm extends ModelForm { } renderForm(): TemplateResult { + const typeOptions = [ + [OutpostTypeEnum.Proxy, msg("Proxy")], + [OutpostTypeEnum.Ldap, msg("LDAP")], + [OutpostTypeEnum.Radius, msg("Radius")], + [OutpostTypeEnum.Rac, msg("RAC")], + ]; + return html` { this.load(); }} > - - - + ${map( + typeOptions, + ([instanceType, label]) => + html` `, + )} @@ -185,45 +221,38 @@ export class OutpostForm extends ModelForm { ?required=${!this.embedded} name="providers" > - -

      - ${msg("You can only select providers that match the type of the outpost.")} -

      -

      - ${msg("Hold control/command to select multiple items.")} -

      +
      - - -

      - ${msg("Set custom attributes using YAML or JSON.")} -

      -

      - ${msg("See more here:")}  - ${msg("Documentation")} -

      -
      `; + + ${msg("Advanced settings")} +
      + + +

      + ${msg("Set custom attributes using YAML or JSON.")} +

      +

      + ${msg("See more here:")}  + ${msg("Documentation")} +

      +
      +
      +
      `; } } diff --git a/web/src/admin/outposts/OutpostHealth.ts b/web/src/admin/outposts/OutpostHealth.ts index 1a97101751..2b0203748e 100644 --- a/web/src/admin/outposts/OutpostHealth.ts +++ b/web/src/admin/outposts/OutpostHealth.ts @@ -34,10 +34,12 @@ export class OutpostHealthElement extends AKElement { } let versionString = this.outpostHealth.version; if (this.outpostHealth.buildHash) { - versionString = `${versionString} (build ${this.outpostHealth.buildHash.substring( - 0, - 8, - )})`; + versionString = msg( + str`${versionString} (build ${this.outpostHealth.buildHash.substring(0, 8)})`, + ); + } + if (this.outpostHealth.fipsEnabled) { + versionString = msg(str`${versionString} (FIPS)`); } return html`
      diff --git a/web/src/admin/outposts/OutpostListPage.ts b/web/src/admin/outposts/OutpostListPage.ts index 390134ad03..5928e5ebb0 100644 --- a/web/src/admin/outposts/OutpostListPage.ts +++ b/web/src/admin/outposts/OutpostListPage.ts @@ -4,9 +4,9 @@ import "@goauthentik/admin/outposts/OutpostForm"; import "@goauthentik/admin/outposts/OutpostHealth"; import "@goauthentik/admin/outposts/OutpostHealthSimple"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { PFSize } from "@goauthentik/common/enums.js"; import { uiConfig } from "@goauthentik/common/ui/config"; import { PFColor } from "@goauthentik/elements/Label"; -import { PFSize } from "@goauthentik/elements/Spinner"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; @@ -41,6 +41,8 @@ export function TypeToLabel(type?: OutpostTypeEnum): string { return msg("LDAP"); case OutpostTypeEnum.Radius: return msg("Radius"); + case OutpostTypeEnum.Rac: + return msg("RAC"); case OutpostTypeEnum.UnknownDefaultOpenApi: return msg("Unknown type"); } @@ -105,6 +107,7 @@ export class OutpostListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "name"; diff --git a/web/src/admin/outposts/ServiceConnectionListPage.ts b/web/src/admin/outposts/ServiceConnectionListPage.ts index a212358f56..e4bea38813 100644 --- a/web/src/admin/outposts/ServiceConnectionListPage.ts +++ b/web/src/admin/outposts/ServiceConnectionListPage.ts @@ -39,6 +39,7 @@ export class OutpostServiceConnectionListPage extends TablePage> { const connections = await new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList( diff --git a/web/src/admin/outposts/ServiceConnectionWizard.ts b/web/src/admin/outposts/ServiceConnectionWizard.ts index 5f221fee31..4feed47476 100644 --- a/web/src/admin/outposts/ServiceConnectionWizard.ts +++ b/web/src/admin/outposts/ServiceConnectionWizard.ts @@ -4,73 +4,24 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/ProxyForm"; import "@goauthentik/elements/wizard/FormWizardPage"; +import "@goauthentik/elements/wizard/TypeCreateWizardPage"; import "@goauthentik/elements/wizard/Wizard"; -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; +import type { Wizard } from "@goauthentik/elements/wizard/Wizard"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { CSSResult, TemplateResult, html } from "lit"; -import { property } from "lit/decorators.js"; +import { property, query } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { OutpostsApi, TypeCreate } from "@goauthentik/api"; -@customElement("ak-service-connection-wizard-initial") -export class InitialServiceConnectionWizardPage extends WizardPage { - @property({ attribute: false }) - connectionTypes: TypeCreate[] = []; - - static get styles(): CSSResult[] { - return [PFBase, PFForm, PFButton, PFRadio]; - } - sidebarLabel = () => msg("Select type"); - - activeCallback: () => Promise = async () => { - this.host.isValid = false; - this.shadowRoot - ?.querySelectorAll("input[type=radio]") - .forEach((radio) => { - if (radio.checked) { - radio.dispatchEvent(new CustomEvent("change")); - } - }); - }; - - render(): TemplateResult { - return html`
      - ${this.connectionTypes.map((type) => { - return html`
      - { - this.host.steps = [ - "initial", - `type-${type.component}-${type.modelName}`, - ]; - this.host.isValid = true; - }} - /> - - ${type.description} -
      `; - })} -
      `; - } -} - @customElement("ak-service-connection-wizard") export class ServiceConnectionWizard extends AKElement { static get styles(): CSSResult[] { - return [PFBase, PFButton, PFRadio]; + return [PFBase, PFButton]; } @property() @@ -79,6 +30,9 @@ export class ServiceConnectionWizard extends AKElement { @property({ attribute: false }) connectionTypes: TypeCreate[] = []; + @query("ak-wizard") + wizard?: Wizard; + firstUpdated(): void { new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllTypesList().then((types) => { this.connectionTypes = types; @@ -92,11 +46,19 @@ export class ServiceConnectionWizard extends AKElement { header=${msg("New outpost integration")} description=${msg("Create a new outpost integration.")} > - ) => { + if (!this.wizard) return; + this.wizard.steps = [ + "initial", + `type-${ev.detail.component}-${ev.detail.modelName}`, + ]; + this.wizard.isValid = true; + }} > - + ${this.connectionTypes.map((type) => { return html` { policyOnly = false; checkbox = true; + clearOnRefresh = true; + + order = "order"; async apiEndpoint(page: number): Promise> { return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({ target: this.target || "", - ordering: "order", + ordering: this.order, page: page, pageSize: (await uiConfig()).pagination.perPage, }); diff --git a/web/src/admin/policies/PolicyBindingForm.ts b/web/src/admin/policies/PolicyBindingForm.ts index c98d0082b2..de711f4139 100644 --- a/web/src/admin/policies/PolicyBindingForm.ts +++ b/web/src/admin/policies/PolicyBindingForm.ts @@ -179,6 +179,7 @@ export class PolicyBindingForm extends ModelForm { .fetchObjects=${async (query?: string): Promise => { const args: CoreGroupsListRequest = { ordering: "name", + includeUsers: false, }; if (query !== undefined) { args.search = query; diff --git a/web/src/admin/policies/PolicyListPage.ts b/web/src/admin/policies/PolicyListPage.ts index 9a1dda2158..b792df2110 100644 --- a/web/src/admin/policies/PolicyListPage.ts +++ b/web/src/admin/policies/PolicyListPage.ts @@ -44,6 +44,7 @@ export class PolicyListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "name"; diff --git a/web/src/admin/policies/PolicyTestForm.ts b/web/src/admin/policies/PolicyTestForm.ts index 50771abbdb..6e57301676 100644 --- a/web/src/admin/policies/PolicyTestForm.ts +++ b/web/src/admin/policies/PolicyTestForm.ts @@ -3,6 +3,7 @@ import { first } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/events/LogViewer"; import { Form } from "@goauthentik/elements/forms/Form"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -85,28 +86,7 @@ export class PolicyTestForm extends Form {
      - ${(this.result?.logMessages || []).length > 0 - ? this.result?.logMessages?.map((m) => { - return html`
      -
      - ${m.log_level} -
      -
      -
      - ${m.event} -
      -
      -
      `; - }) - : html`
      -
      - ${msg("No log messages.")} -
      -
      `} +
      diff --git a/web/src/admin/policies/PolicyWizard.ts b/web/src/admin/policies/PolicyWizard.ts index c1a36fe53a..c66e953249 100644 --- a/web/src/admin/policies/PolicyWizard.ts +++ b/web/src/admin/policies/PolicyWizard.ts @@ -10,80 +10,24 @@ import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/ProxyForm"; import "@goauthentik/elements/wizard/FormWizardPage"; import { FormWizardPage } from "@goauthentik/elements/wizard/FormWizardPage"; +import "@goauthentik/elements/wizard/TypeCreateWizardPage"; import "@goauthentik/elements/wizard/Wizard"; -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; +import type { Wizard } from "@goauthentik/elements/wizard/Wizard"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { CSSResult, TemplateResult, html } from "lit"; -import { property } from "lit/decorators.js"; +import { property, query } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { PoliciesApi, Policy, PolicyBinding, TypeCreate } from "@goauthentik/api"; -@customElement("ak-policy-wizard-initial") -export class InitialPolicyWizardPage extends WizardPage { - @property({ attribute: false }) - policyTypes: TypeCreate[] = []; - - static get styles(): CSSResult[] { - return [PFBase, PFForm, PFButton, PFRadio]; - } - sidebarLabel = () => msg("Select type"); - - activeCallback: () => Promise = async () => { - this.host.isValid = false; - this.shadowRoot - ?.querySelectorAll("input[type=radio]") - .forEach((radio) => { - if (radio.checked) { - radio.dispatchEvent(new CustomEvent("change")); - } - }); - }; - - render(): TemplateResult { - return html`
      - ${this.policyTypes.map((type) => { - return html`
      - { - const idx = this.host.steps.indexOf("initial") + 1; - // Exclude all current steps starting with type-, - // this happens when the user selects a type and then goes back - this.host.steps = this.host.steps.filter( - (step) => !step.startsWith("type-"), - ); - this.host.steps.splice( - idx, - 0, - `type-${type.component}-${type.modelName}`, - ); - this.host.isValid = true; - }} - /> - - ${type.description} -
      `; - })} -
      `; - } -} - @customElement("ak-policy-wizard") export class PolicyWizard extends AKElement { static get styles(): CSSResult[] { - return [PFBase, PFButton, PFRadio]; + return [PFBase, PFButton]; } @property() @@ -98,6 +42,9 @@ export class PolicyWizard extends AKElement { @property({ attribute: false }) policyTypes: TypeCreate[] = []; + @query("ak-wizard") + wizard?: Wizard; + firstUpdated(): void { new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => { this.policyTypes = types; @@ -111,8 +58,26 @@ export class PolicyWizard extends AKElement { header=${msg("New policy")} description=${msg("Create a new policy.")} > - - + ) => { + if (!this.wizard) return; + const idx = this.wizard.steps.indexOf("initial") + 1; + // Exclude all current steps starting with type-, + // this happens when the user selects a type and then goes back + this.wizard.steps = this.wizard.steps.filter( + (step) => !step.startsWith("type-"), + ); + this.wizard.steps.splice( + idx, + 0, + `type-${ev.detail.component}-${ev.detail.modelName}`, + ); + this.wizard.isValid = true; + }} + > + ${this.policyTypes.map((type) => { return html` { order = "identifier"; checkbox = true; + clearOnRefresh = true; async apiEndpoint(page: number): Promise> { return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresList({ @@ -91,10 +93,11 @@ export class ReputationListPage extends TablePage { : html``} ${item.ip}`, html`${item.score}`, - html`${item.updated.toLocaleString()}`, + html`
      ${getRelativeTime(item.updated)}
      + ${item.updated.toLocaleString()}`, html` diff --git a/web/src/admin/property-mappings/PropertyMappingGoogleWorkspaceForm.ts b/web/src/admin/property-mappings/PropertyMappingGoogleWorkspaceForm.ts new file mode 100644 index 0000000000..3677fc3a99 --- /dev/null +++ b/web/src/admin/property-mappings/PropertyMappingGoogleWorkspaceForm.ts @@ -0,0 +1,72 @@ +import { BasePropertyMappingForm } from "@goauthentik/admin/property-mappings/BasePropertyMappingForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { docLink } from "@goauthentik/common/global"; +import "@goauthentik/elements/CodeMirror"; +import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { GoogleWorkspaceProviderMapping, PropertymappingsApi } from "@goauthentik/api"; + +@customElement("ak-property-mapping-google-workspace-form") +export class PropertyMappingGoogleWorkspaceForm extends BasePropertyMappingForm { + loadInstance(pk: string): Promise { + return new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsProviderGoogleWorkspaceRetrieve({ + pmUuid: pk, + }); + } + + async send(data: GoogleWorkspaceProviderMapping): Promise { + if (this.instance) { + return new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsProviderGoogleWorkspaceUpdate({ + pmUuid: this.instance.pk, + googleWorkspaceProviderMappingRequest: data, + }); + } else { + return new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsProviderGoogleWorkspaceCreate({ + googleWorkspaceProviderMappingRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + + + + + +

      + ${msg("Expression using Python.")} + + ${msg("See documentation for a list of all variables.")} + +

      +
      `; + } +} diff --git a/web/src/admin/property-mappings/PropertyMappingLDAPForm.ts b/web/src/admin/property-mappings/PropertyMappingLDAPForm.ts index 0b5afe81b0..4647a3e70a 100644 --- a/web/src/admin/property-mappings/PropertyMappingLDAPForm.ts +++ b/web/src/admin/property-mappings/PropertyMappingLDAPForm.ts @@ -23,7 +23,7 @@ export class PropertyMappingLDAPForm extends BasePropertyMappingForm { if (this.instance) { return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsLdapUpdate({ - pmUuid: this.instance.pk || "", + pmUuid: this.instance.pk, lDAPPropertyMappingRequest: data, }); } else { diff --git a/web/src/admin/property-mappings/PropertyMappingListPage.ts b/web/src/admin/property-mappings/PropertyMappingListPage.ts index e961a744cc..be9eb3ed66 100644 --- a/web/src/admin/property-mappings/PropertyMappingListPage.ts +++ b/web/src/admin/property-mappings/PropertyMappingListPage.ts @@ -1,5 +1,8 @@ +import "@goauthentik/admin/property-mappings/PropertyMappingGoogleWorkspaceForm"; import "@goauthentik/admin/property-mappings/PropertyMappingLDAPForm"; +import "@goauthentik/admin/property-mappings/PropertyMappingMicrosoftEntraForm"; import "@goauthentik/admin/property-mappings/PropertyMappingNotification"; +import "@goauthentik/admin/property-mappings/PropertyMappingRACForm"; import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm"; import "@goauthentik/admin/property-mappings/PropertyMappingSCIMForm"; import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm"; @@ -40,6 +43,7 @@ export class PropertyMappingListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "name"; diff --git a/web/src/admin/property-mappings/PropertyMappingMicrosoftEntraForm.ts b/web/src/admin/property-mappings/PropertyMappingMicrosoftEntraForm.ts new file mode 100644 index 0000000000..c517616f8b --- /dev/null +++ b/web/src/admin/property-mappings/PropertyMappingMicrosoftEntraForm.ts @@ -0,0 +1,72 @@ +import { BasePropertyMappingForm } from "@goauthentik/admin/property-mappings/BasePropertyMappingForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { docLink } from "@goauthentik/common/global"; +import "@goauthentik/elements/CodeMirror"; +import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { MicrosoftEntraProviderMapping, PropertymappingsApi } from "@goauthentik/api"; + +@customElement("ak-property-mapping-microsoft-entra-form") +export class PropertyMappingMicrosoftEntraForm extends BasePropertyMappingForm { + loadInstance(pk: string): Promise { + return new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsProviderMicrosoftEntraRetrieve({ + pmUuid: pk, + }); + } + + async send(data: MicrosoftEntraProviderMapping): Promise { + if (this.instance) { + return new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsProviderMicrosoftEntraUpdate({ + pmUuid: this.instance.pk, + microsoftEntraProviderMappingRequest: data, + }); + } else { + return new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsProviderMicrosoftEntraCreate({ + microsoftEntraProviderMappingRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + + + + + +

      + ${msg("Expression using Python.")} + + ${msg("See documentation for a list of all variables.")} + +

      +
      `; + } +} diff --git a/web/src/admin/property-mappings/PropertyMappingNotification.ts b/web/src/admin/property-mappings/PropertyMappingNotification.ts index 9991d2a32f..fe88b1a8c1 100644 --- a/web/src/admin/property-mappings/PropertyMappingNotification.ts +++ b/web/src/admin/property-mappings/PropertyMappingNotification.ts @@ -29,7 +29,7 @@ export class PropertyMappingNotification extends ModelForm { if (this.instance) { return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsNotificationUpdate({ - pmUuid: this.instance.pk || "", + pmUuid: this.instance.pk, notificationWebhookMappingRequest: data, }); } else { diff --git a/web/src/admin/property-mappings/PropertyMappingRACForm.ts b/web/src/admin/property-mappings/PropertyMappingRACForm.ts new file mode 100644 index 0000000000..23609c7bb7 --- /dev/null +++ b/web/src/admin/property-mappings/PropertyMappingRACForm.ts @@ -0,0 +1,175 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { docLink } from "@goauthentik/common/global"; +import "@goauthentik/elements/CodeMirror"; +import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/forms/Radio"; +import type { RadioOption } from "@goauthentik/elements/forms/Radio"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { PropertymappingsApi, RACPropertyMapping } from "@goauthentik/api"; + +export const staticSettingOptions: RadioOption[] = [ + { + label: msg("Unconfigured"), + value: undefined, + default: true, + description: html`${msg("This option will not be changed by this mapping.")}`, + }, + { + label: msg("Enabled"), + value: "true", + }, + { + label: msg("Disabled"), + value: "false", + }, +]; + +@customElement("ak-property-mapping-rac-form") +export class PropertyMappingLDAPForm extends ModelForm { + loadInstance(pk: string): Promise { + return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRacRetrieve({ + pmUuid: pk, + }); + } + + getSuccessMessage(): string { + if (this.instance) { + return msg("Successfully updated mapping."); + } else { + return msg("Successfully created mapping."); + } + } + + async send(data: RACPropertyMapping): Promise { + if (this.instance) { + return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRacUpdate({ + pmUuid: this.instance.pk, + rACPropertyMappingRequest: data, + }); + } else { + return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRacCreate({ + rACPropertyMappingRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + + + + + ${msg("General settings")} +
      + + + + + + +
      +
      + + ${msg("RDP settings")} +
      + + + + + + + + + + + + + + + + +
      +
      + + ${msg("Advanced settings")} +
      + + + +

      + ${msg("Expression using Python.")} + + ${msg("See documentation for a list of all variables.")} + +

      +
      +
      +
      + `; + } +} diff --git a/web/src/admin/property-mappings/PropertyMappingSAMLForm.ts b/web/src/admin/property-mappings/PropertyMappingSAMLForm.ts index 101455c84c..9371bb5caa 100644 --- a/web/src/admin/property-mappings/PropertyMappingSAMLForm.ts +++ b/web/src/admin/property-mappings/PropertyMappingSAMLForm.ts @@ -23,7 +23,7 @@ export class PropertyMappingSAMLForm extends BasePropertyMappingForm { if (this.instance) { return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSamlUpdate({ - pmUuid: this.instance.pk || "", + pmUuid: this.instance.pk, sAMLPropertyMappingRequest: data, }); } else { diff --git a/web/src/admin/property-mappings/PropertyMappingSCIMForm.ts b/web/src/admin/property-mappings/PropertyMappingSCIMForm.ts index 30993cbbd5..51bcfc65d3 100644 --- a/web/src/admin/property-mappings/PropertyMappingSCIMForm.ts +++ b/web/src/admin/property-mappings/PropertyMappingSCIMForm.ts @@ -23,7 +23,7 @@ export class PropertyMappingSCIMForm extends BasePropertyMappingForm { if (this.instance) { return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScimUpdate({ - pmUuid: this.instance.pk || "", + pmUuid: this.instance.pk, sCIMMappingRequest: data, }); } else { diff --git a/web/src/admin/property-mappings/PropertyMappingScopeForm.ts b/web/src/admin/property-mappings/PropertyMappingScopeForm.ts index 3df60889f8..6403e0c6ec 100644 --- a/web/src/admin/property-mappings/PropertyMappingScopeForm.ts +++ b/web/src/admin/property-mappings/PropertyMappingScopeForm.ts @@ -23,7 +23,7 @@ export class PropertyMappingScopeForm extends BasePropertyMappingForm { if (this.instance) { return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScopeUpdate({ - pmUuid: this.instance.pk || "", + pmUuid: this.instance.pk, scopeMappingRequest: data, }); } else { diff --git a/web/src/admin/property-mappings/PropertyMappingTestForm.ts b/web/src/admin/property-mappings/PropertyMappingTestForm.ts index e81af9a0c8..dff9865ad4 100644 --- a/web/src/admin/property-mappings/PropertyMappingTestForm.ts +++ b/web/src/admin/property-mappings/PropertyMappingTestForm.ts @@ -14,16 +14,18 @@ import { ifDefined } from "lit/directives/if-defined.js"; import { CoreApi, + CoreGroupsListRequest, CoreUsersListRequest, - PolicyTestRequest, + Group, PropertyMapping, + PropertyMappingTestRequest, PropertyMappingTestResult, PropertymappingsApi, User, } from "@goauthentik/api"; @customElement("ak-property-mapping-test-form") -export class PolicyTestForm extends Form { +export class PolicyTestForm extends Form { @property({ attribute: false }) mapping?: PropertyMapping; @@ -31,17 +33,17 @@ export class PolicyTestForm extends Form { result?: PropertyMappingTestResult; @property({ attribute: false }) - request?: PolicyTestRequest; + request?: PropertyMappingTestRequest; getSuccessMessage(): string { return msg("Successfully sent test-request."); } - async send(data: PolicyTestRequest): Promise { + async send(data: PropertyMappingTestRequest): Promise { this.request = data; const result = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTestCreate({ pmUuid: this.mapping?.pk || "", - policyTestRequest: data, + propertyMappingTestRequest: data, formatResult: true, }); return (this.result = result); @@ -84,12 +86,14 @@ export class PolicyTestForm extends Form { user: this.request?.user || 0, context: { ldap: { - name: "test-user", - objectSid: "S-1-5-21-2611707862-2219215769-354220275-1137", - objectClass: "person", displayName: "authentik test user", - sAMAccountName: "sAMAccountName", distinguishedName: "cn=user,ou=users,dc=goauthentik,dc=io", + givenName: "test", + name: "test-user", + objectClass: "person", + objectSid: "S-1-5-21-2611707862-2219215769-354220275-1137", + sAMAccountName: "sAMAccountName", + sn: "user", }, }, }; @@ -120,8 +124,9 @@ export class PolicyTestForm extends Form { } renderForm(): TemplateResult { - return html` + return html` => { const args: CoreUsersListRequest = { ordering: "username", @@ -142,7 +147,32 @@ export class PolicyTestForm extends Form { return user?.pk; }} .selected=${(user: User): boolean => { - return this.request?.user.toString() === user.pk.toString(); + return this.request?.user?.toString() === user.pk.toString(); + }} + > + + + + => { + const args: CoreGroupsListRequest = { + ordering: "name", + }; + if (query !== undefined) { + args.search = query; + } + const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(args); + return groups.results; + }} + .renderElement=${(group: Group): string => { + return group.name; + }} + .value=${(group: Group | undefined): string | undefined => { + return group?.pk; + }} + .selected=${(group: Group): boolean => { + return this.request?.group?.toString() === group.pk.toString(); }} > diff --git a/web/src/admin/property-mappings/PropertyMappingWizard.ts b/web/src/admin/property-mappings/PropertyMappingWizard.ts index 9086546a0b..a8ceea2760 100644 --- a/web/src/admin/property-mappings/PropertyMappingWizard.ts +++ b/web/src/admin/property-mappings/PropertyMappingWizard.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/property-mappings/PropertyMappingLDAPForm"; import "@goauthentik/admin/property-mappings/PropertyMappingNotification"; +import "@goauthentik/admin/property-mappings/PropertyMappingRACForm"; import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm"; import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm"; import "@goauthentik/admin/property-mappings/PropertyMappingTestForm"; @@ -7,82 +8,36 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/ProxyForm"; import "@goauthentik/elements/wizard/FormWizardPage"; +import "@goauthentik/elements/wizard/TypeCreateWizardPage"; import "@goauthentik/elements/wizard/Wizard"; -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; +import type { Wizard } from "@goauthentik/elements/wizard/Wizard"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; -import { CSSResult, TemplateResult, html } from "lit"; -import { property } from "lit/decorators.js"; +import { TemplateResult, html } from "lit"; +import { property, query } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { PropertymappingsApi, TypeCreate } from "@goauthentik/api"; -@customElement("ak-property-mapping-wizard-initial") -export class InitialPropertyMappingWizardPage extends WizardPage { - @property({ attribute: false }) - mappingTypes: TypeCreate[] = []; - - static get styles(): CSSResult[] { - return [PFBase, PFForm, PFButton, PFRadio]; - } - sidebarLabel = () => msg("Select type"); - - activeCallback: () => Promise = async () => { - this.host.isValid = false; - this.shadowRoot - ?.querySelectorAll("input[type=radio]") - .forEach((radio) => { - if (radio.checked) { - radio.dispatchEvent(new CustomEvent("change")); - } - }); - }; - - render(): TemplateResult { - return html`
      - ${this.mappingTypes.map((type) => { - return html`
      - { - this.host.steps = [ - "initial", - `type-${type.component}-${type.modelName}`, - ]; - this.host.isValid = true; - }} - /> - - ${type.description} -
      `; - })} -
      `; - } -} - @customElement("ak-property-mapping-wizard") export class PropertyMappingWizard extends AKElement { - static get styles(): CSSResult[] { - return [PFBase, PFButton, PFRadio]; + static get styles() { + return [PFBase, PFButton]; } @property({ attribute: false }) mappingTypes: TypeCreate[] = []; - firstUpdated(): void { - new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTypesList().then((types) => { - this.mappingTypes = types; - }); + @query("ak-wizard") + wizard?: Wizard; + + async firstUpdated(): Promise { + this.mappingTypes = await new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsAllTypesList(); } render(): TemplateResult { @@ -92,11 +47,19 @@ export class PropertyMappingWizard extends AKElement { header=${msg("New property mapping")} description=${msg("Create a new property mapping.")} > - ) => { + if (!this.wizard) return; + this.wizard.steps = [ + "initial", + `type-${ev.detail.component}-${ev.detail.modelName}`, + ]; + this.wizard.isValid = true; + }} > - + ${this.mappingTypes.map((type) => { return html` { } checkbox = true; + clearOnRefresh = true; @property() order = "name"; diff --git a/web/src/admin/providers/ProviderViewPage.ts b/web/src/admin/providers/ProviderViewPage.ts index 4157081d87..a1e2a4ce2e 100644 --- a/web/src/admin/providers/ProviderViewPage.ts +++ b/web/src/admin/providers/ProviderViewPage.ts @@ -1,6 +1,9 @@ +import "@goauthentik/admin/providers/google_workspace/GoogleWorkspaceProviderViewPage"; import "@goauthentik/admin/providers/ldap/LDAPProviderViewPage"; +import "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderViewPage"; import "@goauthentik/admin/providers/oauth2/OAuth2ProviderViewPage"; import "@goauthentik/admin/providers/proxy/ProxyProviderViewPage"; +import "@goauthentik/admin/providers/rac/RACProviderViewPage"; import "@goauthentik/admin/providers/radius/RadiusProviderViewPage"; import "@goauthentik/admin/providers/saml/SAMLProviderViewPage"; import "@goauthentik/admin/providers/scim/SCIMProviderViewPage"; @@ -65,6 +68,18 @@ export class ProviderViewPage extends AKElement { return html``; + case "ak-provider-rac-form": + return html``; + case "ak-provider-google-workspace-form": + return html``; + case "ak-provider-microsoft-entra-form": + return html``; default: return html`

      Invalid provider type ${this.provider?.component}

      `; } diff --git a/web/src/admin/providers/ProviderWizard.ts b/web/src/admin/providers/ProviderWizard.ts index a65945354b..8e39b8c346 100644 --- a/web/src/admin/providers/ProviderWizard.ts +++ b/web/src/admin/providers/ProviderWizard.ts @@ -1,3 +1,4 @@ +import "@goauthentik/admin/common/ak-license-notice"; import "@goauthentik/admin/providers/ldap/LDAPProviderForm"; import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm"; import "@goauthentik/admin/providers/proxy/ProxyProviderForm"; @@ -6,92 +7,26 @@ import "@goauthentik/admin/providers/saml/SAMLProviderImportForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/ProxyForm"; -import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; import "@goauthentik/elements/wizard/FormWizardPage"; +import "@goauthentik/elements/wizard/TypeCreateWizardPage"; +import { TypeCreateWizardPageLayouts } from "@goauthentik/elements/wizard/TypeCreateWizardPage"; import "@goauthentik/elements/wizard/Wizard"; -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; +import type { Wizard } from "@goauthentik/elements/wizard/Wizard"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { CSSResult, TemplateResult, html } from "lit"; -import { property } from "lit/decorators.js"; +import { property, query } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFHint from "@patternfly/patternfly/components/Hint/hint.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { ProvidersApi, TypeCreate } from "@goauthentik/api"; -@customElement("ak-provider-wizard-initial") -export class InitialProviderWizardPage extends WizardPage { - @property({ attribute: false }) - providerTypes: TypeCreate[] = []; - - static get styles(): CSSResult[] { - return [PFBase, PFForm, PFHint, PFButton, PFRadio]; - } - sidebarLabel = () => msg("Select type"); - - activeCallback: () => Promise = async () => { - this.host.isValid = false; - this.shadowRoot - ?.querySelectorAll("input[type=radio]") - .forEach((radio) => { - if (radio.checked) { - radio.dispatchEvent(new CustomEvent("change")); - } - }); - }; - - renderHint(): TemplateResult { - return html`
      -
      ${msg("Try the new application wizard")}
      -
      - ${msg( - "The new application wizard greatly simplifies the steps required to create applications and providers.", - )} -
      - -
      -
      `; - } - - render(): TemplateResult { - return html`
      - ${this.providerTypes.map((type) => { - return html`
      - { - this.host.steps = ["initial", `type-${type.component}`]; - this.host.isValid = true; - }} - /> - - ${type.description} -
      `; - })} -
      `; - } -} - @customElement("ak-provider-wizard") export class ProviderWizard extends AKElement { static get styles(): CSSResult[] { - return [PFBase, PFButton, PFRadio]; + return [PFBase, PFButton]; } @property() @@ -105,10 +40,11 @@ export class ProviderWizard extends AKElement { return Promise.resolve(); }; - firstUpdated(): void { - new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => { - this.providerTypes = types; - }); + @query("ak-wizard") + wizard?: Wizard; + + async firstUpdated(): Promise { + this.providerTypes = await new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList(); } render(): TemplateResult { @@ -121,8 +57,17 @@ export class ProviderWizard extends AKElement { return this.finalHandler(); }} > - - + ) => { + if (!this.wizard) return; + this.wizard.steps = ["initial", `type-${ev.detail.component}`]; + this.wizard.isValid = true; + }} + > + ${this.providerTypes.map((type) => { return html` { + loadInstance(pk: number): Promise { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceRetrieve({ + id: pk, + }); + } + + async load(): Promise { + this.propertyMappings = await new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsProviderGoogleWorkspaceList({ + ordering: "managed", + }); + } + + propertyMappings?: PaginatedGoogleWorkspaceProviderMappingList; + + async send(data: GoogleWorkspaceProvider): Promise { + if (this.instance) { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceUpdate({ + id: this.instance.pk, + googleWorkspaceProviderRequest: data, + }); + } else { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceCreate({ + googleWorkspaceProviderRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + + + + ${msg("Protocol settings")} +
      + + +

      + ${msg("Google Cloud credentials file.")} +

      +
      + + +

      + ${msg( + "Email address of the user the actions of authentik will be delegated to.", + )} +

      +
      + + +

      + ${msg( + "Default domain that is used to generate a group's email address. Can be customized using property mappings.", + )} +

      +
      + + + + +
      +
      + + ${msg("User filtering")} +
      + + + + + => { + const args: CoreGroupsListRequest = { + ordering: "name", + includeUsers: false, + }; + if (query !== undefined) { + args.search = query; + } + const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList( + args, + ); + return groups.results; + }} + .renderElement=${(group: Group): string => { + return group.name; + }} + .value=${(group: Group | undefined): string | undefined => { + return group ? group.pk : undefined; + }} + .selected=${(group: Group): boolean => { + return group.pk === this.instance?.filterGroup; + }} + ?blankable=${true} + > + +

      + ${msg("Only sync users within the selected group.")} +

      +
      +
      +
      + + ${msg("Attribute mapping")} +
      + + +

      + ${msg("Property mappings used to user mapping.")} +

      +

      + ${msg("Hold control/command to select multiple items.")} +

      +
      + + +

      + ${msg("Property mappings used to group creation.")} +

      +

      + ${msg("Hold control/command to select multiple items.")} +

      +
      +
      +
      `; + } +} diff --git a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts new file mode 100644 index 0000000000..cb6ba6794d --- /dev/null +++ b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList.ts @@ -0,0 +1,73 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { GoogleWorkspaceProviderGroup, ProvidersApi } from "@goauthentik/api"; + +@customElement("ak-provider-google-workspace-groups-list") +export class GoogleWorkspaceProviderGroupList extends Table { + @property({ type: Number }) + providerId?: number; + + expandable = true; + + searchEnabled(): boolean { + return true; + } + + checkbox = true; + clearOnRefresh = true; + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceGroupsDestroy({ + id: item.id, + }); + }} + > + + `; + } + + async apiEndpoint(page: number): Promise> { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceGroupsList({ + page: page, + pageSize: (await uiConfig()).pagination.perPage, + ordering: this.order, + search: this.search || "", + providerId: this.providerId, + }); + } + + columns(): TableColumn[] { + return [new TableColumn(msg("Name")), new TableColumn(msg("ID"))]; + } + + row(item: GoogleWorkspaceProviderGroup): TemplateResult[] { + return [ + html` +
      ${item.groupObj.name}
      +
      `, + html`${item.id}`, + ]; + } + + renderExpanded(item: GoogleWorkspaceProviderGroup): TemplateResult { + return html` +
      +
      ${JSON.stringify(item.attributes, null, 4)}
      +
      + `; + } +} diff --git a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts new file mode 100644 index 0000000000..7312f7ea16 --- /dev/null +++ b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderUserList.ts @@ -0,0 +1,74 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { GoogleWorkspaceProviderUser, ProvidersApi } from "@goauthentik/api"; + +@customElement("ak-provider-google-workspace-users-list") +export class GoogleWorkspaceProviderUserList extends Table { + @property({ type: Number }) + providerId?: number; + + searchEnabled(): boolean { + return true; + } + + expandable = true; + + checkbox = true; + clearOnRefresh = true; + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceUsersDestroy({ + id: item.id, + }); + }} + > + + `; + } + + async apiEndpoint(page: number): Promise> { + return new ProvidersApi(DEFAULT_CONFIG).providersGoogleWorkspaceUsersList({ + page: page, + pageSize: (await uiConfig()).pagination.perPage, + ordering: this.order, + search: this.search || "", + providerId: this.providerId, + }); + } + + columns(): TableColumn[] { + return [new TableColumn(msg("Username")), new TableColumn(msg("ID"))]; + } + + row(item: GoogleWorkspaceProviderUser): TemplateResult[] { + return [ + html` +
      ${item.userObj.username}
      + ${item.userObj.name} +
      `, + html`${item.id}`, + ]; + } + + renderExpanded(item: GoogleWorkspaceProviderUser): TemplateResult { + return html` +
      +
      ${JSON.stringify(item.attributes, null, 4)}
      +
      + `; + } +} diff --git a/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderViewPage.ts b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderViewPage.ts new file mode 100644 index 0000000000..4ca5b16a90 --- /dev/null +++ b/web/src/admin/providers/google_workspace/GoogleWorkspaceProviderViewPage.ts @@ -0,0 +1,223 @@ +import "@goauthentik/admin/providers/google_workspace/GoogleWorkspaceProviderForm"; +import "@goauthentik/admin/providers/google_workspace/GoogleWorkspaceProviderGroupList"; +import "@goauthentik/admin/providers/google_workspace/GoogleWorkspaceProviderUserList"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import "@goauthentik/components/events/ObjectChangelog"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/Markdown"; +import "@goauthentik/elements/SyncStatusCard"; +import "@goauthentik/elements/Tabs"; +import "@goauthentik/elements/buttons/ActionButton"; +import "@goauthentik/elements/buttons/ModalButton"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; + +import { msg } from "@lit/localize"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFList from "@patternfly/patternfly/components/List/list.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { + GoogleWorkspaceProvider, + ProvidersApi, + RbacPermissionsAssignedByUsersListModelEnum, + SyncStatus, +} from "@goauthentik/api"; + +@customElement("ak-provider-google-workspace-view") +export class GoogleWorkspaceProviderViewPage extends AKElement { + @property({ type: Number }) + providerID?: number; + + @state() + provider?: GoogleWorkspaceProvider; + + @state() + syncState?: SyncStatus; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFButton, + PFBanner, + PFForm, + PFFormControl, + PFStack, + PFList, + PFGrid, + PFPage, + PFContent, + PFCard, + PFDescriptionList, + ]; + } + + constructor() { + super(); + this.addEventListener(EVENT_REFRESH, () => { + if (!this.provider?.pk) return; + this.providerID = this.provider?.pk; + }); + } + + fetchProvider(id: number) { + new ProvidersApi(DEFAULT_CONFIG) + .providersGoogleWorkspaceRetrieve({ id }) + .then((prov) => (this.provider = prov)); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("providerID") && this.providerID) { + this.fetchProvider(this.providerID); + } + } + + render(): TemplateResult { + if (!this.provider) { + return html``; + } + return html` +
      { + new ProvidersApi(DEFAULT_CONFIG) + .providersGoogleWorkspaceSyncStatusRetrieve({ + id: this.provider?.pk || 0, + }) + .then((state) => { + this.syncState = state; + }) + .catch(() => { + this.syncState = undefined; + }); + }} + > + ${this.renderTabOverview()} +
      +
      +
      +
      + + +
      +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      + +
      `; + } + + renderTabOverview(): TemplateResult { + if (!this.provider) { + return html``; + } + return html`
      + ${msg("Google Workspace Provider is in preview.")} + ${msg("Send us feedback!")} +
      + ${!this.provider?.assignedBackchannelApplicationName + ? html`
      + ${msg( + "Warning: Provider is not assigned to an application as backchannel provider.", + )} +
      ` + : html``} +
      +
      +
      +
      +
      +
      + ${msg("Name")} +
      +
      +
      + ${this.provider.name} +
      +
      +
      +
      +
      + +
      +
      + { + return new ProvidersApi( + DEFAULT_CONFIG, + ).providersGoogleWorkspaceSyncStatusRetrieve({ + id: this.provider?.pk || 0, + }); + }} + .triggerSync=${() => { + return new ProvidersApi( + DEFAULT_CONFIG, + ).providersGoogleWorkspacePartialUpdate({ + id: this.provider?.pk || 0, + patchedGoogleWorkspaceProviderRequest: {}, + }); + }} + > +
      +
      `; + } +} diff --git a/web/src/admin/providers/ldap/LDAPProviderForm.ts b/web/src/admin/providers/ldap/LDAPProviderForm.ts index 449a3ad5b9..5af4329243 100644 --- a/web/src/admin/providers/ldap/LDAPProviderForm.ts +++ b/web/src/admin/providers/ldap/LDAPProviderForm.ts @@ -1,9 +1,9 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search"; -import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/Radio"; @@ -25,7 +25,7 @@ import { } from "@goauthentik/api"; @customElement("ak-provider-ldap-form") -export class LDAPProviderFormPage extends BaseProviderForm { +export class LDAPProviderFormPage extends WithBrandConfig(BaseProviderForm) { async loadInstance(pk: number): Promise { return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({ id: pk, @@ -35,7 +35,7 @@ export class LDAPProviderFormPage extends BaseProviderForm { async send(data: LDAPProvider): Promise { if (this.instance) { return new ProvidersApi(DEFAULT_CONFIG).providersLdapUpdate({ - id: this.instance.pk || 0, + id: this.instance.pk, lDAPProviderRequest: data, }); } else { @@ -48,7 +48,7 @@ export class LDAPProviderFormPage extends BaseProviderForm { // All Provider objects have an Authorization flow, but not all providers have an Authentication // flow. LDAP needs only one field, but it is not an Authorization field, it is an // Authentication field. So, yeah, we're using the authorization field to store the - // authentication information, which is why the ak-tenanted-flow-search call down there looks so + // authentication information, which is why the ak-branded-flow-search call down there looks so // weird-- we're looking up Authentication flows, but we're storing them in the Authorization // field of the target Provider. renderForm(): TemplateResult { @@ -65,12 +65,12 @@ export class LDAPProviderFormPage extends BaseProviderForm { ?required=${true} name="authorizationFlow" > - + >

      ${msg("Flow used for users to authenticate.")}

      @@ -78,6 +78,7 @@ export class LDAPProviderFormPage extends BaseProviderForm { .fetchObjects=${async (query?: string): Promise => { const args: CoreGroupsListRequest = { ordering: "name", + includeUsers: false, }; if (query !== undefined) { args.search = query; diff --git a/web/src/admin/providers/ldap/LDAPProviderViewPage.ts b/web/src/admin/providers/ldap/LDAPProviderViewPage.ts index 421b043343..39c77166e3 100644 --- a/web/src/admin/providers/ldap/LDAPProviderViewPage.ts +++ b/web/src/admin/providers/ldap/LDAPProviderViewPage.ts @@ -1,6 +1,5 @@ import "@goauthentik/admin/providers/RelatedApplicationButton"; import "@goauthentik/admin/providers/ldap/LDAPProviderForm"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { me } from "@goauthentik/common/users"; @@ -10,9 +9,10 @@ import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -37,21 +37,10 @@ import { @customElement("ak-provider-ldap-view") export class LDAPProviderViewPage extends AKElement { - @property() - set args(value: { [key: string]: number }) { - this.providerID = value.id; - } - @property({ type: Number }) - set providerID(value: number) { - new ProvidersApi(DEFAULT_CONFIG) - .providersLdapRetrieve({ - id: value, - }) - .then((prov) => (this.provider = prov)); - } + providerID?: number; - @property({ attribute: false }) + @state() provider?: LDAPProvider; @state() @@ -84,6 +73,18 @@ export class LDAPProviderViewPage extends AKElement { }); } + fetchProvider(id: number) { + new ProvidersApi(DEFAULT_CONFIG) + .providersLdapRetrieve({ id }) + .then((prov) => (this.provider = prov)); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("providerID") && this.providerID) { + this.fetchProvider(this.providerID); + } + } + render(): TemplateResult { if (!this.provider) { return html``; @@ -203,8 +204,9 @@ export class LDAPProviderViewPage extends AKElement { class="pf-c-form-control" readonly type="text" - value=${`cn=${this.me?.user - .username},ou=users,${this.provider?.baseDn?.toLowerCase()}`} + value=${`cn=${ + this.me?.user.username + },ou=users,${this.provider?.baseDn?.toLowerCase()}`} />
      diff --git a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderFormPage.ts b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderFormPage.ts new file mode 100644 index 0000000000..5943cdb127 --- /dev/null +++ b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderFormPage.ts @@ -0,0 +1,287 @@ +import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import "@goauthentik/elements/forms/Radio"; +import "@goauthentik/elements/forms/SearchSelect"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + CoreApi, + CoreGroupsListRequest, + Group, + MicrosoftEntraProvider, + OutgoingSyncDeleteAction, + PaginatedMicrosoftEntraProviderMappingList, + PropertymappingsApi, + ProvidersApi, +} from "@goauthentik/api"; + +@customElement("ak-provider-microsoft-entra-form") +export class MicrosoftEntraProviderFormPage extends BaseProviderForm { + loadInstance(pk: number): Promise { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraRetrieve({ + id: pk, + }); + } + + async load(): Promise { + this.propertyMappings = await new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsProviderMicrosoftEntraList({ + ordering: "managed", + }); + } + + propertyMappings?: PaginatedMicrosoftEntraProviderMappingList; + + async send(data: MicrosoftEntraProvider): Promise { + if (this.instance) { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraUpdate({ + id: this.instance.pk, + microsoftEntraProviderRequest: data, + }); + } else { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraCreate({ + microsoftEntraProviderRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + + + + ${msg("Protocol settings")} +
      + + +

      + ${msg("Client ID for the app registration.")} +

      +
      + + +

      + ${msg("Client secret for the app registration.")} +

      +
      + + +

      + ${msg("ID of the tenant accounts will be synced into.")} +

      +
      + + + + +
      +
      + + ${msg("User filtering")} +
      + + + + + => { + const args: CoreGroupsListRequest = { + ordering: "name", + includeUsers: false, + }; + if (query !== undefined) { + args.search = query; + } + const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList( + args, + ); + return groups.results; + }} + .renderElement=${(group: Group): string => { + return group.name; + }} + .value=${(group: Group | undefined): string | undefined => { + return group ? group.pk : undefined; + }} + .selected=${(group: Group): boolean => { + return group.pk === this.instance?.filterGroup; + }} + ?blankable=${true} + > + +

      + ${msg("Only sync users within the selected group.")} +

      +
      +
      +
      + + ${msg("Attribute mapping")} +
      + + +

      + ${msg("Property mappings used to user mapping.")} +

      +

      + ${msg("Hold control/command to select multiple items.")} +

      +
      + + +

      + ${msg("Property mappings used to group creation.")} +

      +

      + ${msg("Hold control/command to select multiple items.")} +

      +
      +
      +
      `; + } +} diff --git a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts new file mode 100644 index 0000000000..a77298b8ef --- /dev/null +++ b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList.ts @@ -0,0 +1,70 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { MicrosoftEntraProviderGroup, ProvidersApi } from "@goauthentik/api"; + +@customElement("ak-provider-microsoft-entra-groups-list") +export class MicrosoftEntraProviderGroupList extends Table { + @property({ type: Number }) + providerId?: number; + + expandable = true; + + searchEnabled(): boolean { + return true; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraGroupsDestroy({ + id: item.id, + }); + }} + > + + `; + } + + async apiEndpoint(page: number): Promise> { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraGroupsList({ + page: page, + pageSize: (await uiConfig()).pagination.perPage, + ordering: this.order, + search: this.search || "", + providerId: this.providerId, + }); + } + + columns(): TableColumn[] { + return [new TableColumn(msg("Name")), new TableColumn(msg("ID"))]; + } + + row(item: MicrosoftEntraProviderGroup): TemplateResult[] { + return [ + html` +
      ${item.groupObj.name}
      +
      `, + html`${item.id}`, + ]; + } + + renderExpanded(item: MicrosoftEntraProviderGroup): TemplateResult { + return html` +
      +
      ${JSON.stringify(item.attributes, null, 4)}
      +
      + `; + } +} diff --git a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts new file mode 100644 index 0000000000..f65da7c522 --- /dev/null +++ b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList.ts @@ -0,0 +1,74 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { MicrosoftEntraProviderUser, ProvidersApi } from "@goauthentik/api"; + +@customElement("ak-provider-microsoft-entra-users-list") +export class MicrosoftEntraProviderUserList extends Table { + @property({ type: Number }) + providerId?: number; + + expandable = true; + + searchEnabled(): boolean { + return true; + } + + checkbox = true; + clearOnRefresh = true; + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraUsersDestroy({ + id: item.id, + }); + }} + > + + `; + } + + async apiEndpoint(page: number): Promise> { + return new ProvidersApi(DEFAULT_CONFIG).providersMicrosoftEntraUsersList({ + page: page, + pageSize: (await uiConfig()).pagination.perPage, + ordering: this.order, + search: this.search || "", + providerId: this.providerId, + }); + } + + columns(): TableColumn[] { + return [new TableColumn(msg("Username")), new TableColumn(msg("ID"))]; + } + + row(item: MicrosoftEntraProviderUser): TemplateResult[] { + return [ + html` +
      ${item.userObj.username}
      + ${item.userObj.name} +
      `, + html`${item.id}`, + ]; + } + + renderExpanded(item: MicrosoftEntraProviderUser): TemplateResult { + return html` +
      +
      ${JSON.stringify(item.attributes, null, 4)}
      +
      + `; + } +} diff --git a/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderViewPage.ts b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderViewPage.ts new file mode 100644 index 0000000000..c92c944639 --- /dev/null +++ b/web/src/admin/providers/microsoft_entra/MicrosoftEntraProviderViewPage.ts @@ -0,0 +1,224 @@ +import "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderFormPage"; +import "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderGroupList"; +import "@goauthentik/admin/providers/microsoft_entra/MicrosoftEntraProviderUserList"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import "@goauthentik/components/events/ObjectChangelog"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/Markdown"; +import "@goauthentik/elements/Tabs"; +import "@goauthentik/elements/buttons/ActionButton"; +import "@goauthentik/elements/buttons/ModalButton"; +import "@goauthentik/elements/events/LogViewer"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; + +import { msg } from "@lit/localize"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFList from "@patternfly/patternfly/components/List/list.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { + MicrosoftEntraProvider, + ProvidersApi, + RbacPermissionsAssignedByUsersListModelEnum, + SyncStatus, +} from "@goauthentik/api"; + +@customElement("ak-provider-microsoft-entra-view") +export class MicrosoftEntraProviderViewPage extends AKElement { + @property({ type: Number }) + providerID?: number; + + @state() + provider?: MicrosoftEntraProvider; + + @state() + syncState?: SyncStatus; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFButton, + PFBanner, + PFForm, + PFFormControl, + PFStack, + PFList, + PFGrid, + PFPage, + PFContent, + PFCard, + PFDescriptionList, + ]; + } + + constructor() { + super(); + this.addEventListener(EVENT_REFRESH, () => { + if (!this.provider?.pk) return; + this.providerID = this.provider?.pk; + }); + } + + fetchProvider(id: number) { + new ProvidersApi(DEFAULT_CONFIG) + .providersMicrosoftEntraRetrieve({ id }) + .then((prov) => (this.provider = prov)); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("providerID") && this.providerID) { + this.fetchProvider(this.providerID); + } + } + + render(): TemplateResult { + if (!this.provider) { + return html``; + } + return html` +
      { + new ProvidersApi(DEFAULT_CONFIG) + .providersMicrosoftEntraSyncStatusRetrieve({ + id: this.provider?.pk || 0, + }) + .then((state) => { + this.syncState = state; + }) + .catch(() => { + this.syncState = undefined; + }); + }} + > + ${this.renderTabOverview()} +
      +
      +
      +
      + + +
      +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      + +
      `; + } + + renderTabOverview(): TemplateResult { + if (!this.provider) { + return html``; + } + return html`
      + ${msg("Microsoft Entra Provider is in preview.")} + ${msg("Send us feedback!")} +
      + ${!this.provider?.assignedBackchannelApplicationName + ? html`
      + ${msg( + "Warning: Provider is not assigned to an application as backchannel provider.", + )} +
      ` + : html``} +
      +
      +
      +
      +
      +
      + ${msg("Name")} +
      +
      +
      + ${this.provider.name} +
      +
      +
      +
      +
      + +
      + +
      + { + return new ProvidersApi( + DEFAULT_CONFIG, + ).providersMicrosoftEntraSyncStatusRetrieve({ + id: this.provider?.pk || 0, + }); + }} + .triggerSync=${() => { + return new ProvidersApi( + DEFAULT_CONFIG, + ).providersMicrosoftEntraPartialUpdate({ + id: this.provider?.pk || 0, + patchedMicrosoftEntraProviderRequest: {}, + }); + }} + > +
      +
      `; + } +} diff --git a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts index 000df8cad4..f3f1f49096 100644 --- a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts +++ b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts @@ -48,6 +48,12 @@ export const clientTypeOptions = [ }, ]; +export const defaultScopes = [ + "goauthentik.io/providers/oauth2/scope-openid", + "goauthentik.io/providers/oauth2/scope-email", + "goauthentik.io/providers/oauth2/scope-profile", +]; + export const subjectModeOptions = [ { label: msg("Based on the User's hashed ID"), @@ -146,7 +152,7 @@ export class OAuth2ProviderFormPage extends BaseProviderForm { async send(data: OAuth2Provider): Promise { if (this.instance) { return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Update({ - id: this.instance.pk || 0, + id: this.instance.pk, oAuth2ProviderRequest: data, }); } else { @@ -289,10 +295,9 @@ export class OAuth2ProviderFormPage extends BaseProviderForm { ${this.propertyMappings?.results.map((scope) => { let selected = false; if (!provider?.propertyMappings) { - selected = - scope.managed?.startsWith( - "goauthentik.io/providers/oauth2/scope-", - ) || false; + selected = scope.managed + ? defaultScopes.includes(scope.managed) + : false; } else { selected = Array.from(provider?.propertyMappings).some((su) => { return su == scope.pk; diff --git a/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts b/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts index afe5dfd582..6c3bcc4cc8 100644 --- a/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts +++ b/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts @@ -2,7 +2,7 @@ import "@goauthentik/admin/providers/RelatedApplicationButton"; import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; -import { convertToTitle } from "@goauthentik/common/utils"; +import renderDescriptionList from "@goauthentik/components/DescriptionList"; import "@goauthentik/components/events/ObjectChangelog"; import MDProviderOAuth2 from "@goauthentik/docs/providers/oauth2/index.md"; import { AKElement } from "@goauthentik/elements/Base"; @@ -22,6 +22,7 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; +import PFDivider from "@patternfly/patternfly/components/Divider/divider.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; @@ -29,13 +30,29 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { + ClientTypeEnum, + CoreApi, + CoreUsersListRequest, OAuth2Provider, OAuth2ProviderSetupURLs, PropertyMappingPreview, ProvidersApi, RbacPermissionsAssignedByUsersListModelEnum, + User, } from "@goauthentik/api"; +export function TypeToLabel(type?: ClientTypeEnum): string { + if (!type) return ""; + switch (type) { + case ClientTypeEnum.Confidential: + return msg("Confidential"); + case ClientTypeEnum.Public: + return msg("Public"); + case ClientTypeEnum.UnknownDefaultOpenApi: + return msg("Unknown type"); + } +} + @customElement("ak-provider-oauth2-view") export class OAuth2ProviderViewPage extends AKElement { @property({ type: Number }) @@ -58,6 +75,9 @@ export class OAuth2ProviderViewPage extends AKElement { @state() preview?: PropertyMappingPreview; + @state() + previewUser?: User; + static get styles(): CSSResult[] { return [ PFBase, @@ -70,6 +90,7 @@ export class OAuth2ProviderViewPage extends AKElement { PFForm, PFFormControl, PFBanner, + PFDivider, ]; } @@ -81,6 +102,15 @@ export class OAuth2ProviderViewPage extends AKElement { }); } + fetchPreview(): void { + new ProvidersApi(DEFAULT_CONFIG) + .providersOauth2PreviewUserRetrieve({ + id: this.provider?.pk || 0, + forUser: this.previewUser?.pk, + }) + .then((preview) => (this.preview = preview)); + } + render(): TemplateResult { if (!this.provider) { return html``; @@ -105,11 +135,7 @@ export class OAuth2ProviderViewPage extends AKElement { slot="page-preview" data-tab-title="${msg("Preview")}" @activate=${() => { - new ProvidersApi(DEFAULT_CONFIG) - .providersOauth2PreviewUserRetrieve({ - id: this.provider?.pk || 0, - }) - .then((preview) => (this.preview = preview)); + this.fetchPreview(); }} > ${this.renderTabPreview()} @@ -149,7 +175,7 @@ export class OAuth2ProviderViewPage extends AKElement {
      `}
      @@ -184,7 +210,7 @@ export class OAuth2ProviderViewPage extends AKElement {
      - ${convertToTitle(this.provider.clientType || "")} + ${TypeToLabel(this.provider.clientType)}
      @@ -229,7 +255,7 @@ export class OAuth2ProviderViewPage extends AKElement {
      -
      +
      @@ -258,7 +284,7 @@ export class OAuth2ProviderViewPage extends AKElement { value="${this.providerUrls?.issuer || msg("-")}" />
      -
      +
      @@ -352,8 +380,50 @@ export class OAuth2ProviderViewPage extends AKElement { class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter" >
      -
      - ${msg("Example JWT payload (for currently authenticated user)")} +
      ${msg("JWT payload")}
      +
      + ${renderDescriptionList( + [ + [ + msg("Preview for user"), + html` + => { + const args: CoreUsersListRequest = { + ordering: "username", + }; + if (query !== undefined) { + args.search = query; + } + const users = await new CoreApi( + DEFAULT_CONFIG, + ).coreUsersList(args); + return users.results; + }} + .renderElement=${(user: User): string => { + return user.username; + }} + .renderDescription=${(user: User): TemplateResult => { + return html`${user.name}`; + }} + .value=${(user: User | undefined): number | undefined => { + return user?.pk; + }} + .selected=${(user: User): boolean => { + return user.pk === this.previewUser?.pk; + }} + ?blankable=${true} + @ak-change=${(ev: CustomEvent) => { + this.previewUser = ev.detail.value; + this.fetchPreview(); + }} + > + + `, + ], + ], + { horizontal: true }, + )}
      ${this.preview diff --git a/web/src/admin/providers/proxy/ProxyProviderForm.ts b/web/src/admin/providers/proxy/ProxyProviderForm.ts index ddd5545725..d05c433912 100644 --- a/web/src/admin/providers/proxy/ProxyProviderForm.ts +++ b/web/src/admin/providers/proxy/ProxyProviderForm.ts @@ -72,7 +72,7 @@ export class ProxyProviderFormPage extends BaseProviderForm { } if (this.instance) { return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({ - id: this.instance.pk || 0, + id: this.instance.pk, proxyProviderRequest: data, }); } else { diff --git a/web/src/admin/providers/proxy/ProxyProviderViewPage.ts b/web/src/admin/providers/proxy/ProxyProviderViewPage.ts index bdce041940..e4488af237 100644 --- a/web/src/admin/providers/proxy/ProxyProviderViewPage.ts +++ b/web/src/admin/providers/proxy/ProxyProviderViewPage.ts @@ -1,6 +1,5 @@ import "@goauthentik/admin/providers/RelatedApplicationButton"; import "@goauthentik/admin/providers/proxy/ProxyProviderForm"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { convertToSlug } from "@goauthentik/common/utils"; @@ -22,11 +21,12 @@ import { Replacer } from "@goauthentik/elements/Markdown"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -75,21 +75,10 @@ export function isForward(mode: ProxyMode): boolean { @customElement("ak-provider-proxy-view") export class ProxyProviderViewPage extends AKElement { - @property() - set args(value: { [key: string]: number }) { - this.providerID = value.id; - } - @property({ type: Number }) - set providerID(value: number) { - new ProvidersApi(DEFAULT_CONFIG) - .providersProxyRetrieve({ - id: value, - }) - .then((prov) => (this.provider = prov)); - } + providerID?: number; - @property({ attribute: false }) + @state() provider?: ProxyProvider; static get styles(): CSSResult[] { @@ -116,35 +105,54 @@ export class ProxyProviderViewPage extends AKElement { }); } + fetchProvider(id: number) { + new ProvidersApi(DEFAULT_CONFIG) + .providersProxyRetrieve({ id }) + .then((prov) => (this.provider = prov)); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("providerID") && this.providerID) { + this.fetchProvider(this.providerID); + } + } + renderConfig(): TemplateResult { const serves = [ { label: msg("Nginx (Ingress)"), md: MDNginxIngress, + meta: "providers/proxy/_nginx_ingress.md", }, { label: msg("Nginx (Proxy Manager)"), md: MDNginxPM, + meta: "providers/proxy/_nginx_proxy_manager.md", }, { label: msg("Nginx (standalone)"), md: MDNginxStandalone, + meta: "providers/proxy/_nginx_standalone.md", }, { label: msg("Traefik (Ingress)"), md: MDTraefikIngress, + meta: "providers/proxy/_traefik_ingress.md", }, { label: msg("Traefik (Compose)"), md: MDTraefikCompose, + meta: "providers/proxy/_traefik_compose.md", }, { label: msg("Traefik (Standalone)"), md: MDTraefikStandalone, + meta: "providers/proxy/_traefik_standalone.md", }, { label: msg("Caddy (Standalone)"), md: MDCaddyStandalone, + meta: "providers/proxy/_caddy_standalone.md", }, ]; const replacers: Replacer[] = [ @@ -182,7 +190,11 @@ export class ProxyProviderViewPage extends AKElement { data-tab-title="${server.label}" class="pf-c-page__main-section pf-m-light pf-m-no-padding-mobile" > - + `; })}`; @@ -248,7 +260,10 @@ export class ProxyProviderViewPage extends AKElement {
      - +
      `; diff --git a/web/src/admin/providers/rac/ConnectionTokenList.ts b/web/src/admin/providers/rac/ConnectionTokenList.ts new file mode 100644 index 0000000000..d9d4fbf830 --- /dev/null +++ b/web/src/admin/providers/rac/ConnectionTokenList.ts @@ -0,0 +1,98 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@goauthentik/elements/forms/ModalForm"; +import { PaginatedResponse, Table } from "@goauthentik/elements/table/Table"; +import { TableColumn } from "@goauthentik/elements/table/Table"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; + +import { ConnectionToken, RACProvider, RacApi } from "@goauthentik/api"; + +@customElement("ak-rac-connection-token-list") +export class ConnectionTokenListPage extends Table { + checkbox = true; + clearOnRefresh = true; + + searchEnabled(): boolean { + return true; + } + + @property() + order = "name"; + + @property({ attribute: false }) + provider?: RACProvider; + + @property({ type: Number }) + userId?: number; + + static get styles(): CSSResult[] { + return super.styles.concat(PFDescriptionList); + } + + async apiEndpoint(page: number): Promise> { + return new RacApi(DEFAULT_CONFIG).racConnectionTokensList({ + ordering: this.order, + page: page, + pageSize: (await uiConfig()).pagination.perPage, + search: this.search || "", + provider: this.provider?.pk, + sessionUser: this.userId, + }); + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return [ + { key: msg("Endpoint"), value: item.endpointObj.name }, + { key: msg("User"), value: item.user.username }, + ]; + }} + .usedBy=${(item: ConnectionToken) => { + return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({ + connectionTokenUuid: item.pk || "", + }); + }} + .delete=${(item: ConnectionToken) => { + return new RacApi(DEFAULT_CONFIG).racConnectionTokensDestroy({ + connectionTokenUuid: item.pk || "", + }); + }} + > + + `; + } + + columns(): TableColumn[] { + if (this.provider) { + return [ + new TableColumn(msg("Endpoint"), "endpoint__name"), + new TableColumn(msg("User"), "session__user"), + ]; + } + return [ + new TableColumn(msg("Provider"), "provider__name"), + new TableColumn(msg("Endpoint"), "endpoint__name"), + ]; + } + + row(item: ConnectionToken): TemplateResult[] { + if (this.provider) { + return [html`${item.endpointObj.name}`, html`${item.user.username}`]; + } + return [html`${item.providerObj.name}`, html`${item.endpointObj.name}`]; + } +} diff --git a/web/src/admin/providers/rac/EndpointForm.ts b/web/src/admin/providers/rac/EndpointForm.ts new file mode 100644 index 0000000000..c14feee462 --- /dev/null +++ b/web/src/admin/providers/rac/EndpointForm.ts @@ -0,0 +1,159 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-radio-input"; +import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import YAML from "yaml"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + AuthModeEnum, + Endpoint, + PaginatedRACPropertyMappingList, + PropertymappingsApi, + ProtocolEnum, + RacApi, +} from "@goauthentik/api"; + +@customElement("ak-rac-endpoint-form") +export class EndpointForm extends ModelForm { + @property({ type: Number }) + providerID?: number; + + propertyMappings?: PaginatedRACPropertyMappingList; + + async load(): Promise { + this.propertyMappings = await new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsRacList({ + ordering: "name", + }); + } + + loadInstance(pk: string): Promise { + return new RacApi(DEFAULT_CONFIG).racEndpointsRetrieve({ + pbmUuid: pk, + }); + } + + getSuccessMessage(): string { + return this.instance + ? msg("Successfully updated endpoint.") + : msg("Successfully created endpoint."); + } + + async send(data: Endpoint): Promise { + data.authMode = AuthModeEnum.Prompt; + if (!this.instance) { + data.provider = this.providerID || 0; + } else { + data.provider = this.instance.provider; + } + if (this.instance) { + return new RacApi(DEFAULT_CONFIG).racEndpointsPartialUpdate({ + pbmUuid: this.instance.pk || "", + patchedEndpointRequest: data, + }); + } else { + return new RacApi(DEFAULT_CONFIG).racEndpointsCreate({ + endpointRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + + + + + + + + + +

      ${msg("Hostname/IP to connect to.")}

      +
      + + +

      + ${msg( + "Maximum concurrent allowed connections to this endpoint. Can be set to -1 to disable the limit.", + )} +

      +
      + + +

      + ${msg("Hold control/command to select multiple items.")} +

      +
      + + ${msg("Advanced settings")} +
      + + + +

      ${msg("Connection settings.")}

      +
      +
      +
      + `; + } +} diff --git a/web/src/admin/providers/rac/EndpointList.ts b/web/src/admin/providers/rac/EndpointList.ts new file mode 100644 index 0000000000..fb17887875 --- /dev/null +++ b/web/src/admin/providers/rac/EndpointList.ts @@ -0,0 +1,143 @@ +import "@goauthentik/admin/policies/BoundPoliciesList"; +import "@goauthentik/admin/providers/rac/EndpointForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionModal"; +import { PaginatedResponse, Table } from "@goauthentik/elements/table/Table"; +import { TableColumn } from "@goauthentik/elements/table/Table"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; + +import { + Endpoint, + RACProvider, + RacApi, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; + +@customElement("ak-rac-endpoint-list") +export class EndpointListPage extends Table { + expandable = true; + checkbox = true; + clearOnRefresh = true; + + searchEnabled(): boolean { + return true; + } + + @property() + order = "name"; + + @property({ attribute: false }) + provider?: RACProvider; + + static get styles(): CSSResult[] { + return super.styles.concat(PFDescriptionList); + } + + async apiEndpoint(page: number): Promise> { + return new RacApi(DEFAULT_CONFIG).racEndpointsList({ + ordering: this.order, + page: page, + pageSize: (await uiConfig()).pagination.perPage, + search: this.search || "", + provider: this.provider?.pk, + superuserFullList: true, + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn(msg("Name"), "name"), + new TableColumn(msg("Host"), "host"), + new TableColumn(msg("Actions")), + ]; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return [ + { key: msg("Name"), value: item.name }, + { key: msg("Host"), value: item.host }, + ]; + }} + .usedBy=${(item: Endpoint) => { + return new RacApi(DEFAULT_CONFIG).racEndpointsUsedByList({ + pbmUuid: item.pk, + }); + }} + .delete=${(item: Endpoint) => { + return new RacApi(DEFAULT_CONFIG).racEndpointsDestroy({ + pbmUuid: item.pk, + }); + }} + > + + `; + } + + row(item: Endpoint): TemplateResult[] { + return [ + html`${item.name}`, + html`${item.host}`, + html` + ${msg("Update")} + ${msg("Update Endpoint")} + + + + + + `, + ]; + } + + renderExpanded(item: Endpoint): TemplateResult { + return html` + +
      +
      +

      + ${msg( + "These bindings control which users will have access to this endpoint. Users must also have access to the application.", + )} +

      + +
      +
      + `; + } + + renderObjectCreate(): TemplateResult { + return html` + + ${msg("Create")} + ${msg("Create Endpoint")} + + + + + `; + } +} diff --git a/web/src/admin/providers/rac/RACProviderForm.ts b/web/src/admin/providers/rac/RACProviderForm.ts new file mode 100644 index 0000000000..0fef06697e --- /dev/null +++ b/web/src/admin/providers/rac/RACProviderForm.ts @@ -0,0 +1,174 @@ +import "@goauthentik/admin/common/ak-crypto-certificate-search"; +import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; +import "@goauthentik/elements/forms/Radio"; +import "@goauthentik/elements/forms/SearchSelect"; +import "@goauthentik/elements/utils/TimeDeltaHelp"; +import YAML from "yaml"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + FlowsInstancesListDesignationEnum, + PaginatedRACPropertyMappingList, + PropertymappingsApi, + ProvidersApi, + RACProvider, +} from "@goauthentik/api"; + +@customElement("ak-provider-rac-form") +export class RACProviderFormPage extends ModelForm { + @state() + propertyMappings?: PaginatedRACPropertyMappingList; + + async load(): Promise { + this.propertyMappings = await new PropertymappingsApi( + DEFAULT_CONFIG, + ).propertymappingsRacList({ + ordering: "name", + }); + } + + async loadInstance(pk: number): Promise { + return new ProvidersApi(DEFAULT_CONFIG).providersRacRetrieve({ + id: pk, + }); + } + + getSuccessMessage(): string { + if (this.instance) { + return msg("Successfully updated provider."); + } else { + return msg("Successfully created provider."); + } + } + + async send(data: RACProvider): Promise { + if (this.instance) { + return new ProvidersApi(DEFAULT_CONFIG).providersRacUpdate({ + id: this.instance.pk, + rACProviderRequest: data, + }); + } else { + return new ProvidersApi(DEFAULT_CONFIG).providersRacCreate({ + rACProviderRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + + + + + + +

      + ${msg("Flow used when authorizing this provider.")} +

      +
      + + +

      + ${msg( + "Determines how long a session lasts before being disconnected and requiring re-authorization.", + )} +

      + +
      + + +

      + ${msg( + "When enabled, connection authorizations will be deleted when a client disconnects. This will force clients with flaky internet connections to re-authorize the endpoint.", + )} +

      +
      + + + ${msg("Protocol settings")} +
      + + +

      + ${msg("Hold control/command to select multiple items.")} +

      +
      + + + +

      ${msg("Connection settings.")}

      +
      +
      +
      + `; + } +} diff --git a/web/src/admin/providers/rac/RACProviderViewPage.ts b/web/src/admin/providers/rac/RACProviderViewPage.ts new file mode 100644 index 0000000000..efa10e7a1d --- /dev/null +++ b/web/src/admin/providers/rac/RACProviderViewPage.ts @@ -0,0 +1,196 @@ +import "@goauthentik/admin/providers/RelatedApplicationButton"; +import "@goauthentik/admin/providers/rac/ConnectionTokenList"; +import "@goauthentik/admin/providers/rac/EndpointForm"; +import "@goauthentik/admin/providers/rac/EndpointList"; +import "@goauthentik/admin/providers/rac/RACProviderForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import "@goauthentik/components/ak-status-label"; +import "@goauthentik/components/events/ObjectChangelog"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/Tabs"; +import "@goauthentik/elements/buttons/ModalButton"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; + +import { msg } from "@lit/localize"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFList from "@patternfly/patternfly/components/List/list.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { + ProvidersApi, + RACProvider, + RbacPermissionsAssignedByUsersListModelEnum, +} from "@goauthentik/api"; + +@customElement("ak-provider-rac-view") +export class RACProviderViewPage extends AKElement { + @property({ type: Number }) + providerID?: number; + + @state() + provider?: RACProvider; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFButton, + PFPage, + PFGrid, + PFContent, + PFList, + PFForm, + PFFormControl, + PFCard, + PFDescriptionList, + PFBanner, + ]; + } + + constructor() { + super(); + this.addEventListener(EVENT_REFRESH, () => { + if (!this.provider?.pk) return; + this.providerID = this.provider?.pk; + }); + } + + fetchProvider(id: number) { + new ProvidersApi(DEFAULT_CONFIG) + .providersRacRetrieve({ id }) + .then((prov) => (this.provider = prov)); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("providerID") && this.providerID) { + this.fetchProvider(this.providerID); + } + } + + render(): TemplateResult { + if (!this.provider) { + return html``; + } + return html` +
      + ${this.renderTabOverview()} +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + +
      +
      +
      + +
      `; + } + + renderTabOverview(): TemplateResult { + if (!this.provider) { + return html``; + } + return html`
      + ${msg("RAC is in preview.")} + ${msg("Send us feedback!")} +
      + ${this.provider?.assignedApplicationName + ? html`` + : html`
      + ${msg("Warning: Provider is not used by an Application.")} +
      `} + ${this.provider?.outpostSet.length < 1 + ? html`
      + ${msg("Warning: Provider is not used by any Outpost.")} +
      ` + : html``} +
      +
      +
      +
      +
      +
      + ${msg("Name")} +
      +
      +
      + ${this.provider.name} +
      +
      +
      +
      +
      + ${msg("Assigned to application")} +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      ${msg("Endpoints")}
      +
      + +
      +
      +
      `; + } +} diff --git a/web/src/admin/providers/radius/RadiusProviderForm.ts b/web/src/admin/providers/radius/RadiusProviderForm.ts index 269a5ee957..fd6d74ef00 100644 --- a/web/src/admin/providers/radius/RadiusProviderForm.ts +++ b/web/src/admin/providers/radius/RadiusProviderForm.ts @@ -1,7 +1,7 @@ import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -14,7 +14,7 @@ import { customElement } from "lit/decorators.js"; import { FlowsInstancesListDesignationEnum, ProvidersApi, RadiusProvider } from "@goauthentik/api"; @customElement("ak-provider-radius-form") -export class RadiusProviderFormPage extends BaseProviderForm { +export class RadiusProviderFormPage extends WithBrandConfig(BaseProviderForm) { loadInstance(pk: number): Promise { return new ProvidersApi(DEFAULT_CONFIG).providersRadiusRetrieve({ id: pk, @@ -24,7 +24,7 @@ export class RadiusProviderFormPage extends BaseProviderForm { async send(data: RadiusProvider): Promise { if (this.instance) { return new ProvidersApi(DEFAULT_CONFIG).providersRadiusUpdate({ - id: this.instance.pk || 0, + id: this.instance.pk, radiusProviderRequest: data, }); } else { @@ -37,7 +37,7 @@ export class RadiusProviderFormPage extends BaseProviderForm { // All Provider objects have an Authorization flow, but not all providers have an Authentication // flow. Radius needs only one field, but it is not the Authorization field, it is an // Authentication field. So, yeah, we're using the authorization field to store the - // authentication information, which is why the ak-tenanted-flow-search call down there looks so + // authentication information, which is why the ak-branded-flow-search call down there looks so // weird-- we're looking up Authentication flows, but we're storing them in the Authorization // field of the target Provider. renderForm(): TemplateResult { @@ -54,12 +54,12 @@ export class RadiusProviderFormPage extends BaseProviderForm { ?required=${true} name="authorizationFlow" > - + >

      ${msg("Flow used for users to authenticate.")}

      diff --git a/web/src/admin/providers/radius/RadiusProviderViewPage.ts b/web/src/admin/providers/radius/RadiusProviderViewPage.ts index 3963face9f..c4d90fdf27 100644 --- a/web/src/admin/providers/radius/RadiusProviderViewPage.ts +++ b/web/src/admin/providers/radius/RadiusProviderViewPage.ts @@ -1,6 +1,5 @@ import "@goauthentik/admin/providers/RelatedApplicationButton"; import "@goauthentik/admin/providers/radius/RadiusProviderForm"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -9,10 +8,11 @@ import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; @@ -32,21 +32,10 @@ import { @customElement("ak-provider-radius-view") export class RadiusProviderViewPage extends AKElement { - @property() - set args(value: { [key: string]: number }) { - this.providerID = value.id; - } - @property({ type: Number }) - set providerID(value: number) { - new ProvidersApi(DEFAULT_CONFIG) - .providersRadiusRetrieve({ - id: value, - }) - .then((prov) => (this.provider = prov)); - } + providerID?: number; - @property({ attribute: false }) + @state() provider?: RadiusProvider; static get styles(): CSSResult[] { @@ -71,6 +60,18 @@ export class RadiusProviderViewPage extends AKElement { }); } + fetchProvider(id: number) { + new ProvidersApi(DEFAULT_CONFIG) + .providersRadiusRetrieve({ id }) + .then((prov) => (this.provider = prov)); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("providerID") && this.providerID) { + this.fetchProvider(this.providerID); + } + } + render(): TemplateResult { if (!this.provider) { return html``; diff --git a/web/src/admin/providers/saml/SAMLProviderForm.ts b/web/src/admin/providers/saml/SAMLProviderForm.ts index 006a32545a..9cb5670d0f 100644 --- a/web/src/admin/providers/saml/SAMLProviderForm.ts +++ b/web/src/admin/providers/saml/SAMLProviderForm.ts @@ -47,7 +47,7 @@ export class SAMLProviderFormPage extends BaseProviderForm { async send(data: SAMLProvider): Promise { if (this.instance) { return new ProvidersApi(DEFAULT_CONFIG).providersSamlUpdate({ - id: this.instance.pk || 0, + id: this.instance.pk, sAMLProviderRequest: data, }); } else { @@ -191,7 +191,6 @@ export class SAMLProviderFormPage extends BaseProviderForm { @@ -185,7 +185,6 @@ export class SCIMProviderFormPage extends BaseProviderForm { { - this.host.steps = [ - "initial", - `type-${type.component}-${type.modelName}`, - ]; - this.host.isValid = true; - }} - /> - - ${type.description} -
      `; - })} - `; - } -} - @customElement("ak-source-wizard") export class SourceWizard extends AKElement { static get styles(): CSSResult[] { - return [PFBase, PFButton, PFRadio]; + return [PFBase, PFButton]; } @property({ attribute: false }) sourceTypes: TypeCreate[] = []; + @query("ak-wizard") + wizard?: Wizard; + firstUpdated(): void { new SourcesApi(DEFAULT_CONFIG).sourcesAllTypesList().then((types) => { this.sourceTypes = types; @@ -91,8 +46,20 @@ export class SourceWizard extends AKElement { header=${msg("New source")} description=${msg("Create a new source.")} > - - + ) => { + if (!this.wizard) return; + this.wizard.steps = [ + "initial", + `type-${ev.detail.component}-${ev.detail.modelName}`, + ]; + this.wizard.isValid = true; + }} + > + ${this.sourceTypes.map((type) => { return html` { ${msg("Enabled")} + + +

      + ${msg( + "When the user logs in to authentik using this source password backend, update their credentials in authentik.", + )} +

      +
      -
      -
      -

      ${msg("Sync status")}

      -
      -
      ${this.renderSyncStatus()}
      - +
      + { + return new SourcesApi(DEFAULT_CONFIG).sourcesLdapSyncStatusRetrieve( + { + slug: this.source?.slug, + }, + ); + }} + .triggerSync=${() => { + return new SourcesApi(DEFAULT_CONFIG).sourcesLdapPartialUpdate({ + slug: this.source?.slug || "", + patchedLDAPSourceRequest: {}, + }); + }} + >
      diff --git a/web/src/admin/sources/oauth/OAuthSourceForm.ts b/web/src/admin/sources/oauth/OAuthSourceForm.ts index cf0209fcb3..b63f8b92e1 100644 --- a/web/src/admin/sources/oauth/OAuthSourceForm.ts +++ b/web/src/admin/sources/oauth/OAuthSourceForm.ts @@ -4,20 +4,22 @@ import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; import { msg } from "@lit/localize"; -import { TemplateResult, html } from "lit"; +import { PropertyValues, TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { - CapabilitiesEnum, FlowsInstancesListDesignationEnum, OAuthSource, OAuthSourceRequest, @@ -28,7 +30,7 @@ import { } from "@goauthentik/api"; @customElement("ak-source-oauth-form") -export class OAuthSourceForm extends BaseSourceForm { +export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm) { async loadInstance(pk: string): Promise { const source = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthRetrieve({ slug: pk, @@ -38,22 +40,8 @@ export class OAuthSourceForm extends BaseSourceForm { return source; } - _modelName?: string; - @property() - set modelName(v: string | undefined) { - this._modelName = v; - new SourcesApi(DEFAULT_CONFIG) - .sourcesOauthSourceTypesList({ - name: v?.replace("oauthsource", ""), - }) - .then((type) => { - this.providerType = type[0]; - }); - } - get modelName(): string | undefined { - return this._modelName; - } + modelName?: string; @property({ attribute: false }) providerType: SourceType | null = null; @@ -62,7 +50,7 @@ export class OAuthSourceForm extends BaseSourceForm { clearIcon = false; async send(data: OAuthSource): Promise { - data.providerType = (this.providerType?.slug || "") as ProviderTypeEnum; + data.providerType = (this.providerType?.name || "") as ProviderTypeEnum; let source: OAuthSource; if (this.instance) { source = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthPartialUpdate({ @@ -95,6 +83,22 @@ export class OAuthSourceForm extends BaseSourceForm { return source; } + fetchProviderType(v: string | undefined) { + new SourcesApi(DEFAULT_CONFIG) + .sourcesOauthSourceTypesList({ + name: v?.replace("oauthsource", ""), + }) + .then((type) => { + this.providerType = type[0]; + }); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("modelName")) { + this.fetchProviderType(this.modelName); + } + } + renderUrlOptions(): TemplateResult { if (!this.providerType?.urlsCustomizable) { return html``; @@ -104,7 +108,6 @@ export class OAuthSourceForm extends BaseSourceForm {
      { "", )}" class="pf-c-form-control" - required />

      ${msg("URL the user is redirect to to consent the authorization.")}

      - +

      ${msg("URL used by authentik to retrieve tokens.")}

      - +

      ${msg("URL used by authentik to get user information.")} @@ -176,7 +168,7 @@ export class OAuthSourceForm extends BaseSourceForm {

      ` : html``} - ${this.providerType.slug === ProviderTypeEnum.Openidconnect || + ${this.providerType.name === ProviderTypeEnum.Openidconnect || this.providerType.oidcWellKnownUrl !== "" ? html` {

      ` : html``} - ${this.providerType.slug === ProviderTypeEnum.Openidconnect || + ${this.providerType.name === ProviderTypeEnum.Openidconnect || this.providerType.oidcJwksUrl !== "" ? html` { />

      ${placeholderHelperText}

      - ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` ${this.instance?.icon diff --git a/web/src/admin/sources/oauth/OAuthSourceViewPage.ts b/web/src/admin/sources/oauth/OAuthSourceViewPage.ts index f70c13038e..0d4d1035d1 100644 --- a/web/src/admin/sources/oauth/OAuthSourceViewPage.ts +++ b/web/src/admin/sources/oauth/OAuthSourceViewPage.ts @@ -1,7 +1,6 @@ import "@goauthentik/admin/policies/BoundPoliciesList"; import "@goauthentik/admin/sources/oauth/OAuthSourceDiagram"; import "@goauthentik/admin/sources/oauth/OAuthSourceForm"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -10,6 +9,7 @@ import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; @@ -44,6 +44,8 @@ export function ProviderToLabel(provider?: ProviderTypeEnum): string { return "Facebook"; case ProviderTypeEnum.Github: return "GitHub"; + case ProviderTypeEnum.Gitlab: + return "GitLab"; case ProviderTypeEnum.Google: return "Google"; case ProviderTypeEnum.Mailcow: diff --git a/web/src/admin/sources/plex/PlexSourceForm.ts b/web/src/admin/sources/plex/PlexSourceForm.ts index 8091067ff2..8444e11128 100644 --- a/web/src/admin/sources/plex/PlexSourceForm.ts +++ b/web/src/admin/sources/plex/PlexSourceForm.ts @@ -2,10 +2,13 @@ import "@goauthentik/admin/common/ak-flow-search/ak-source-flow-search"; import { iconHelperText, placeholderHelperText } from "@goauthentik/admin/helperText"; import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; -import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; @@ -16,7 +19,6 @@ import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { - CapabilitiesEnum, FlowsInstancesListDesignationEnum, PlexSource, SourcesApi, @@ -24,7 +26,7 @@ import { } from "@goauthentik/api"; @customElement("ak-source-plex-form") -export class PlexSourceForm extends BaseSourceForm { +export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm) { async loadInstance(pk: string): Promise { const source = await new SourcesApi(DEFAULT_CONFIG).sourcesPlexRetrieve({ slug: pk, @@ -63,8 +65,7 @@ export class PlexSourceForm extends BaseSourceForm { plexSourceRequest: data, }); } - const c = await config(); - if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) { + if (this.can(CapabilitiesEnum.CanSaveMedia)) { const icon = this.getFormFiles()["icon"]; if (icon || this.clearIcon) { await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({ @@ -255,7 +256,7 @@ export class PlexSourceForm extends BaseSourceForm { />

      ${placeholderHelperText}

      - ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` ${this.instance?.icon diff --git a/web/src/admin/sources/plex/PlexSourceViewPage.ts b/web/src/admin/sources/plex/PlexSourceViewPage.ts index 88287a8b2f..a1bf00c4bb 100644 --- a/web/src/admin/sources/plex/PlexSourceViewPage.ts +++ b/web/src/admin/sources/plex/PlexSourceViewPage.ts @@ -1,6 +1,5 @@ import "@goauthentik/admin/policies/BoundPoliciesList"; import "@goauthentik/admin/sources/plex/PlexSourceForm"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -9,6 +8,7 @@ import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; diff --git a/web/src/admin/sources/saml/SAMLSourceForm.ts b/web/src/admin/sources/saml/SAMLSourceForm.ts index 76e996322a..c969411fb8 100644 --- a/web/src/admin/sources/saml/SAMLSourceForm.ts +++ b/web/src/admin/sources/saml/SAMLSourceForm.ts @@ -5,7 +5,10 @@ import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils"; import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; -import { rootInterface } from "@goauthentik/elements/Base"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/Radio"; @@ -18,7 +21,6 @@ import { ifDefined } from "lit/directives/if-defined.js"; import { BindingTypeEnum, - CapabilitiesEnum, DigestAlgorithmEnum, FlowsInstancesListDesignationEnum, NameIdPolicyEnum, @@ -29,7 +31,7 @@ import { } from "@goauthentik/api"; @customElement("ak-source-saml-form") -export class SAMLSourceForm extends BaseSourceForm { +export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm) { @state() clearIcon = false; @@ -149,7 +151,7 @@ export class SAMLSourceForm extends BaseSourceForm { - ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia) + ${this.can(CapabilitiesEnum.CanSaveMedia) ? html` ${this.instance?.icon diff --git a/web/src/admin/sources/saml/SAMLSourceViewPage.ts b/web/src/admin/sources/saml/SAMLSourceViewPage.ts index 61abaf5946..4f5d39e104 100644 --- a/web/src/admin/sources/saml/SAMLSourceViewPage.ts +++ b/web/src/admin/sources/saml/SAMLSourceViewPage.ts @@ -1,6 +1,5 @@ import "@goauthentik/admin/policies/BoundPoliciesList"; import "@goauthentik/admin/sources/saml/SAMLSourceForm"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/components/events/ObjectChangelog"; @@ -10,6 +9,7 @@ import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; diff --git a/web/src/admin/sources/scim/SCIMSourceForm.ts b/web/src/admin/sources/scim/SCIMSourceForm.ts new file mode 100644 index 0000000000..1dbe38f1bb --- /dev/null +++ b/web/src/admin/sources/scim/SCIMSourceForm.ts @@ -0,0 +1,86 @@ +import { placeholderHelperText } from "@goauthentik/admin/helperText"; +import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/elements/forms/FormGroup"; +import "@goauthentik/elements/forms/HorizontalFormElement"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { SCIMSource, SCIMSourceRequest, SourcesApi } from "@goauthentik/api"; + +@customElement("ak-source-scim-form") +export class SCIMSourceForm extends BaseSourceForm { + async loadInstance(pk: string): Promise { + return new SourcesApi(DEFAULT_CONFIG) + .sourcesScimRetrieve({ + slug: pk, + }) + .then((source) => { + return source; + }); + } + + async send(data: SCIMSource): Promise { + if (this.instance?.slug) { + return new SourcesApi(DEFAULT_CONFIG).sourcesScimPartialUpdate({ + slug: this.instance.slug, + patchedSCIMSourceRequest: data, + }); + } else { + return new SourcesApi(DEFAULT_CONFIG).sourcesScimCreate({ + sCIMSourceRequest: data as unknown as SCIMSourceRequest, + }); + } + } + + renderForm(): TemplateResult { + return html`
      + + + + + + + +
      + + +
      +
      + + ${msg("Advanced protocol settings")} +
      + + +

      ${placeholderHelperText}

      +
      +
      +
      +
      `; + } +} diff --git a/web/src/admin/sources/scim/SCIMSourceGroups.ts b/web/src/admin/sources/scim/SCIMSourceGroups.ts new file mode 100644 index 0000000000..46e14c7c33 --- /dev/null +++ b/web/src/admin/sources/scim/SCIMSourceGroups.ts @@ -0,0 +1,51 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { SCIMSourceGroup, SourcesApi } from "@goauthentik/api"; + +@customElement("ak-source-scim-groups-list") +export class SCIMSourceGroupList extends Table { + @property() + sourceSlug?: string; + + expandable = true; + searchEnabled(): boolean { + return true; + } + + async apiEndpoint(page: number): Promise> { + return new SourcesApi(DEFAULT_CONFIG).sourcesScimGroupsList({ + page: page, + pageSize: (await uiConfig()).pagination.perPage, + ordering: this.order, + search: this.search || "", + sourceSlug: this.sourceSlug, + }); + } + + columns(): TableColumn[] { + return [new TableColumn(msg("Name")), new TableColumn(msg("ID"))]; + } + + renderExpanded(item: SCIMSourceGroup): TemplateResult { + return html` +
      +
      ${JSON.stringify(item.attributes, null, 4)}
      +
      + `; + } + + row(item: SCIMSourceGroup): TemplateResult[] { + return [ + html` +
      ${item.groupObj.name}
      +
      `, + html`${item.id}`, + ]; + } +} diff --git a/web/src/admin/sources/scim/SCIMSourceUsers.ts b/web/src/admin/sources/scim/SCIMSourceUsers.ts new file mode 100644 index 0000000000..1e753c39cf --- /dev/null +++ b/web/src/admin/sources/scim/SCIMSourceUsers.ts @@ -0,0 +1,52 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { SCIMSourceUser, SourcesApi } from "@goauthentik/api"; + +@customElement("ak-source-scim-users-list") +export class SCIMSourceUserList extends Table { + @property() + sourceSlug?: string; + + expandable = true; + searchEnabled(): boolean { + return true; + } + + async apiEndpoint(page: number): Promise> { + return new SourcesApi(DEFAULT_CONFIG).sourcesScimUsersList({ + page: page, + pageSize: (await uiConfig()).pagination.perPage, + ordering: this.order, + search: this.search || "", + sourceSlug: this.sourceSlug, + }); + } + + columns(): TableColumn[] { + return [new TableColumn(msg("Username")), new TableColumn(msg("ID"))]; + } + + renderExpanded(item: SCIMSourceUser): TemplateResult { + return html` +
      +
      ${JSON.stringify(item.attributes, null, 4)}
      +
      + `; + } + + row(item: SCIMSourceUser): TemplateResult[] { + return [ + html` +
      ${item.userObj.username}
      + ${item.userObj.name} +
      `, + html`${item.id}`, + ]; + } +} diff --git a/web/src/admin/sources/scim/SCIMSourceViewPage.ts b/web/src/admin/sources/scim/SCIMSourceViewPage.ts new file mode 100644 index 0000000000..9e11ef46be --- /dev/null +++ b/web/src/admin/sources/scim/SCIMSourceViewPage.ts @@ -0,0 +1,215 @@ +import "@goauthentik/admin/sources/scim/SCIMSourceForm"; +import "@goauthentik/admin/sources/scim/SCIMSourceGroups"; +import "@goauthentik/admin/sources/scim/SCIMSourceUsers"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import "@goauthentik/components/events/ObjectChangelog"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/Tabs"; +import "@goauthentik/elements/buttons/ActionButton"; +import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/buttons/TokenCopyButton"; +import "@goauthentik/elements/forms/ModalForm"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { + RbacPermissionsAssignedByUsersListModelEnum, + SCIMSource, + SourcesApi, +} from "@goauthentik/api"; + +@customElement("ak-source-scim-view") +export class SCIMSourceViewPage extends AKElement { + @property({ type: String }) + set sourceSlug(value: string) { + new SourcesApi(DEFAULT_CONFIG) + .sourcesScimRetrieve({ + slug: value, + }) + .then((source) => { + this.source = source; + }); + } + + @property({ attribute: false }) + source?: SCIMSource; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFPage, + PFButton, + PFForm, + PFFormControl, + PFGrid, + PFContent, + PFCard, + PFDescriptionList, + PFBanner, + ]; + } + + constructor() { + super(); + this.addEventListener(EVENT_REFRESH, () => { + if (!this.source?.pk) return; + this.sourceSlug = this.source?.slug; + }); + } + + render(): TemplateResult { + if (!this.source) { + return html``; + } + return html` +
      +
      + ${msg("SCIM Source is in preview.")} + ${msg("Send us feedback!")} +
      +
      +
      +
      +
      +
      +
      + ${msg("Name")} +
      +
      +
      + ${this.source.name} +
      +
      +
      +
      +
      + ${msg("Slug")} +
      +
      +
      + ${this.source.slug} +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + +
      +
      + +
      + + ${msg("Click to copy token")} + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + + +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      + +
      `; + } +} diff --git a/web/src/admin/stages/StageListPage.ts b/web/src/admin/stages/StageListPage.ts index fb28cf42dc..32d80bfe0c 100644 --- a/web/src/admin/stages/StageListPage.ts +++ b/web/src/admin/stages/StageListPage.ts @@ -5,7 +5,7 @@ import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm"; import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm"; import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm"; import "@goauthentik/admin/stages/authenticator_validate/AuthenticatorValidateStageForm"; -import "@goauthentik/admin/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm"; +import "@goauthentik/admin/stages/authenticator_webauthn/AuthenticatorWebAuthnStageForm"; import "@goauthentik/admin/stages/captcha/CaptchaStageForm"; import "@goauthentik/admin/stages/consent/ConsentStageForm"; import "@goauthentik/admin/stages/deny/DenyStageForm"; @@ -15,6 +15,7 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm"; import "@goauthentik/admin/stages/invitation/InvitationStageForm"; import "@goauthentik/admin/stages/password/PasswordStageForm"; import "@goauthentik/admin/stages/prompt/PromptStageForm"; +import "@goauthentik/admin/stages/source/SourceStageForm"; import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm"; import "@goauthentik/admin/stages/user_login/UserLoginStageForm"; import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm"; @@ -55,6 +56,7 @@ export class StageListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "name"; diff --git a/web/src/admin/stages/StageWizard.ts b/web/src/admin/stages/StageWizard.ts index 3ace712e4d..0e5d483d0d 100644 --- a/web/src/admin/stages/StageWizard.ts +++ b/web/src/admin/stages/StageWizard.ts @@ -1,10 +1,11 @@ +import "@goauthentik/admin/common/ak-license-notice"; import { StageBindingForm } from "@goauthentik/admin/flows/StageBindingForm"; import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm"; import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm"; import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm"; import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm"; import "@goauthentik/admin/stages/authenticator_validate/AuthenticatorValidateStageForm"; -import "@goauthentik/admin/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm"; +import "@goauthentik/admin/stages/authenticator_webauthn/AuthenticatorWebAuthnStageForm"; import "@goauthentik/admin/stages/captcha/CaptchaStageForm"; import "@goauthentik/admin/stages/consent/ConsentStageForm"; import "@goauthentik/admin/stages/deny/DenyStageForm"; @@ -14,6 +15,7 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm"; import "@goauthentik/admin/stages/invitation/InvitationStageForm"; import "@goauthentik/admin/stages/password/PasswordStageForm"; import "@goauthentik/admin/stages/prompt/PromptStageForm"; +import "@goauthentik/admin/stages/source/SourceStageForm"; import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm"; import "@goauthentik/admin/stages/user_login/UserLoginStageForm"; import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm"; @@ -23,80 +25,24 @@ import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/ProxyForm"; import "@goauthentik/elements/wizard/FormWizardPage"; import { FormWizardPage } from "@goauthentik/elements/wizard/FormWizardPage"; +import "@goauthentik/elements/wizard/TypeCreateWizardPage"; import "@goauthentik/elements/wizard/Wizard"; -import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; +import { Wizard } from "@goauthentik/elements/wizard/Wizard"; import { msg, str } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { CSSResult, TemplateResult, html } from "lit"; -import { property } from "lit/decorators.js"; +import { property, query } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { FlowStageBinding, Stage, StagesApi, TypeCreate } from "@goauthentik/api"; -@customElement("ak-stage-wizard-initial") -export class InitialStageWizardPage extends WizardPage { - @property({ attribute: false }) - stageTypes: TypeCreate[] = []; - sidebarLabel = () => msg("Select type"); - - static get styles(): CSSResult[] { - return [PFBase, PFForm, PFButton, PFRadio]; - } - - activeCallback: () => Promise = async () => { - this.host.isValid = false; - this.shadowRoot - ?.querySelectorAll("input[type=radio]") - .forEach((radio) => { - if (radio.checked) { - radio.dispatchEvent(new CustomEvent("change")); - } - }); - }; - - render(): TemplateResult { - return html`
      - ${this.stageTypes.map((type) => { - return html`
      - { - const idx = this.host.steps.indexOf("initial") + 1; - // Exclude all current steps starting with type-, - // this happens when the user selects a type and then goes back - this.host.steps = this.host.steps.filter( - (step) => !step.startsWith("type-"), - ); - this.host.steps.splice( - idx, - 0, - `type-${type.component}-${type.modelName}`, - ); - this.host.isValid = true; - }} - /> - - ${type.description} -
      `; - })} -
      `; - } -} - @customElement("ak-stage-wizard") export class StageWizard extends AKElement { static get styles(): CSSResult[] { - return [PFBase, PFButton, PFRadio]; + return [PFBase, PFButton]; } @property() @@ -111,6 +57,9 @@ export class StageWizard extends AKElement { @property({ attribute: false }) stageTypes: TypeCreate[] = []; + @query("ak-wizard") + wizard?: Wizard; + firstUpdated(): void { new StagesApi(DEFAULT_CONFIG).stagesAllTypesList().then((types) => { this.stageTypes = types; @@ -124,8 +73,26 @@ export class StageWizard extends AKElement { header=${msg("New stage")} description=${msg("Create a new stage.")} > - - + ) => { + if (!this.wizard) return; + const idx = this.wizard.steps.indexOf("initial") + 1; + // Exclude all current steps starting with type-, + // this happens when the user selects a type and then goes back + this.wizard.steps = this.wizard.steps.filter( + (step) => !step.startsWith("type-"), + ); + this.wizard.steps.splice( + idx, + 0, + `type-${ev.detail.component}-${ev.detail.modelName}`, + ); + this.wizard.isValid = true; + }} + > + ${this.stageTypes.map((type) => { return html` + const authenticators = [ + [DeviceClassesEnum.Static, msg("Static Tokens")], + [DeviceClassesEnum.Totp, msg("TOTP Authenticators")], + [DeviceClassesEnum.Webauthn, msg("WebAuthn Authenticators")], + [DeviceClassesEnum.Duo, msg("Duo Authenticators")], + [DeviceClassesEnum.Sms, msg("SMS-based Authenticators")], + ]; + + return html` + ${msg( "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows.", )} @@ -84,44 +97,19 @@ export class AuthenticatorValidateStageForm extends BaseStageForm - + authenticator[0]) + .filter((name) => + this.isDeviceClassSelected(name as DeviceClassesEnum), + )} + >

      ${msg("Device classes which can be used to authenticate.")}

      -

      - ${msg("Hold control/command to select multiple items.")} -

      ${msg( - "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped.", + "If the user has successfully authenticated with a device in the classes listed above within this configured duration, this stage will be skipped.", )}

      @@ -183,33 +171,6 @@ export class AuthenticatorValidateStageForm extends BaseStageForm
      - - - - ${this.showConfigurationStages ? html` - `; + + + ${msg("WebAuthn-specific settings")} +
      + + + + + + => { + return new StagesApi(DEFAULT_CONFIG) + .stagesAuthenticatorWebauthnDeviceTypesList({ + page: page, + search: search, + }) + .then((results) => { + return { + pagination: results.pagination, + options: results.results.map(deviceTypeRestrictionPair), + }; + }); + }} + .selected=${(this.instance?.webauthnAllowedDeviceTypesObj ?? []).map( + deviceTypeRestrictionPair, + )} + available-label="${msg("Available Device types")}" + selected-label="${msg("Selected Device types")}" + > +

      + ${msg( + "Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.", + )} +

      + + ${ + /* TODO: Remove this after 2024.6..or maybe later? */ + msg( + "This restriction only applies to devices created in authentik 2024.4 or later.", + ) + } + +
      +
      +
      + `; } } diff --git a/web/src/admin/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts b/web/src/admin/stages/authenticator_webauthn/AuthenticatorWebAuthnStageForm.ts similarity index 78% rename from web/src/admin/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts rename to web/src/admin/stages/authenticator_webauthn/AuthenticatorWebAuthnStageForm.ts index d8464b6f3d..104d31047f 100644 --- a/web/src/admin/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts +++ b/web/src/admin/stages/authenticator_webauthn/AuthenticatorWebAuthnStageForm.ts @@ -1,7 +1,10 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; +import { deviceTypeRestrictionPair } from "@goauthentik/admin/stages/authenticator_webauthn/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first } from "@goauthentik/common/utils"; +import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider"; +import { DataProvision } from "@goauthentik/elements/ak-dual-select/types"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/SearchSelect"; @@ -11,8 +14,8 @@ import { TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; import { - AuthenticateWebAuthnStage, AuthenticatorAttachmentEnum, + AuthenticatorWebAuthnStage, Flow, FlowsApi, FlowsInstancesListDesignationEnum, @@ -23,25 +26,25 @@ import { } from "@goauthentik/api"; @customElement("ak-stage-authenticator-webauthn-form") -export class AuthenticateWebAuthnStageForm extends BaseStageForm { - loadInstance(pk: string): Promise { - return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnRetrieve({ +export class AuthenticatorWebAuthnStageForm extends BaseStageForm { + async loadInstance(pk: string): Promise { + return await new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnRetrieve({ stageUuid: pk, }); } - async send(data: AuthenticateWebAuthnStage): Promise { + async send(data: AuthenticatorWebAuthnStage): Promise { if (data.authenticatorAttachment?.toString() === "") { data.authenticatorAttachment = null; } if (this.instance) { return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnUpdate({ stageUuid: this.instance.pk || "", - authenticateWebAuthnStageRequest: data, + authenticatorWebAuthnStageRequest: data, }); } else { return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnCreate({ - authenticateWebAuthnStageRequest: data, + authenticatorWebAuthnStageRequest: data, }); } } @@ -164,6 +167,36 @@ export class AuthenticateWebAuthnStageForm extends BaseStageForm
      + + => { + return new StagesApi(DEFAULT_CONFIG) + .stagesAuthenticatorWebauthnDeviceTypesList({ + page: page, + search: search, + }) + .then((results) => { + return { + pagination: results.pagination, + options: results.results.map(deviceTypeRestrictionPair), + }; + }); + }} + .selected=${(this.instance?.deviceTypeRestrictionsObj ?? []).map( + deviceTypeRestrictionPair, + )} + available-label="${msg("Available Device types")}" + selected-label="${msg("Selected Device types")}" + > +

      + ${msg( + "Optionally restrict which WebAuthn device types may be used. When no device types are selected, all devices are allowed.", + )} +

      +
      ${label}
      +
      ${item.aaguid}
      `, + label, + ]; +} diff --git a/web/src/admin/stages/captcha/CaptchaStageForm.ts b/web/src/admin/stages/captcha/CaptchaStageForm.ts index cacdee5dea..27a9470989 100644 --- a/web/src/admin/stages/captcha/CaptchaStageForm.ts +++ b/web/src/admin/stages/captcha/CaptchaStageForm.ts @@ -1,5 +1,7 @@ import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { first } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-number-input"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; @@ -78,6 +80,40 @@ export class CaptchaStageForm extends BaseStageForm { )}

      + + + + +

      + ${msg( + "When enabled and the resultant score is outside the threshold, the user will not be able to continue. When disabled, the user will be able to continue and the score can be used in policies to customize further stages.", + )} +

      +
      diff --git a/web/src/admin/stages/identification/IdentificationStageForm.ts b/web/src/admin/stages/identification/IdentificationStageForm.ts index cc064adf1c..75db877160 100644 --- a/web/src/admin/stages/identification/IdentificationStageForm.ts +++ b/web/src/admin/stages/identification/IdentificationStageForm.ts @@ -2,12 +2,13 @@ import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { first, groupBy } from "@goauthentik/common/utils"; +import "@goauthentik/elements/ak-checkbox-group/ak-checkbox-group.js"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/SearchSelect"; import { msg } from "@lit/localize"; -import { TemplateResult, html } from "lit"; +import { TemplateResult, css, html } from "lit"; import { customElement } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -24,6 +25,17 @@ import { @customElement("ak-stage-identification-form") export class IdentificationStageForm extends BaseStageForm { + static get styles() { + return [ + ...super.styles, + css` + ak-checkbox-group::part(checkbox-group) { + padding-top: var(--pf-c-form--m-horizontal__group-label--md--PaddingTop); + } + `, + ]; + } + loadInstance(pk: string): Promise { return new StagesApi(DEFAULT_CONFIG).stagesIdentificationRetrieve({ stageUuid: pk, @@ -60,6 +72,12 @@ export class IdentificationStageForm extends BaseStageForm } renderForm(): TemplateResult { + const userSelectFields = [ + { name: UserFieldsEnum.Username, label: msg("Username") }, + { name: UserFieldsEnum.Email, label: msg("Email") }, + { name: UserFieldsEnum.Upn, label: msg("UPN") }, + ]; + return html` ${msg("Let the user identify themselves with their username or Email address.")} @@ -75,34 +93,18 @@ export class IdentificationStageForm extends BaseStageForm ${msg("Stage-specific settings")}
      - + name) + .filter((name) => this.isUserFieldSelected(name))} + >

      ${msg( "Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources.", )}

      -

      - ${msg("Hold control/command to select multiple items.")} -

      name="sources" >

      ${msg( diff --git a/web/src/admin/stages/invitation/InvitationForm.ts b/web/src/admin/stages/invitation/InvitationForm.ts index 24b2c34cf7..bd1c8ff9f3 100644 --- a/web/src/admin/stages/invitation/InvitationForm.ts +++ b/web/src/admin/stages/invitation/InvitationForm.ts @@ -65,7 +65,7 @@ export class InvitationForm extends ModelForm { value="${dateTimeLocal(first(this.instance?.expires, new Date()))}" /> - + { } checkbox = true; + clearOnRefresh = true; @property() order = "expires"; @@ -62,20 +63,24 @@ export class InvitationListPage extends TablePage { multipleEnrollmentFlows = false; async apiEndpoint(page: number): Promise> { - // Check if any invitation stages exist - const stages = await new StagesApi(DEFAULT_CONFIG).stagesInvitationStagesList({ - noFlows: false, - }); - this.invitationStageExists = stages.pagination.count > 0; - this.expandable = this.invitationStageExists; - stages.results.forEach((stage) => { - const enrollmentFlows = (stage.flowSet || []).filter( - (flow) => flow.designation === FlowDesignationEnum.Enrollment, - ); - if (enrollmentFlows.length > 1) { - this.multipleEnrollmentFlows = true; - } - }); + try { + // Check if any invitation stages exist + const stages = await new StagesApi(DEFAULT_CONFIG).stagesInvitationStagesList({ + noFlows: false, + }); + this.invitationStageExists = stages.pagination.count > 0; + this.expandable = this.invitationStageExists; + stages.results.forEach((stage) => { + const enrollmentFlows = (stage.flowSet || []).filter( + (flow) => flow.designation === FlowDesignationEnum.Enrollment, + ); + if (enrollmentFlows.length > 1) { + this.multipleEnrollmentFlows = true; + } + }); + } catch { + // assuming we can't fetch stages, ignore the error + } return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsList({ ordering: this.order, page: page, diff --git a/web/src/admin/stages/password/PasswordStageForm.ts b/web/src/admin/stages/password/PasswordStageForm.ts index 939f929315..2732f8c749 100644 --- a/web/src/admin/stages/password/PasswordStageForm.ts +++ b/web/src/admin/stages/password/PasswordStageForm.ts @@ -54,6 +54,21 @@ export class PasswordStageForm extends BaseStageForm { } renderForm(): TemplateResult { + const backends = [ + { + name: BackendsEnum.CoreAuthInbuiltBackend, + label: msg("User database + standard password"), + }, + { + name: BackendsEnum.CoreAuthTokenBackend, + label: msg("User database + app passwords"), + }, + { + name: BackendsEnum.SourcesLdapAuthLdapBackend, + label: msg("User database + LDAP password"), + }, + ]; + return html` ${msg("Validate the user's password against the selected backend(s).")} @@ -73,32 +88,13 @@ export class PasswordStageForm extends BaseStageForm { ?required=${true} name="backends" > - + name) + .filter((name) => this.isBackendSelected(name))} + >

      ${msg("Selection of backends to test the password against.")}

      diff --git a/web/src/admin/stages/prompt/PromptForm.ts b/web/src/admin/stages/prompt/PromptForm.ts index bddf1c368f..234d0a8bb0 100644 --- a/web/src/admin/stages/prompt/PromptForm.ts +++ b/web/src/admin/stages/prompt/PromptForm.ts @@ -11,6 +11,7 @@ import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; import { customElement, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; +import { map } from "lit/directives/map.js"; import PFTitle from "@patternfly/patternfly/components/Title/title.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; @@ -28,7 +29,7 @@ class PreviewStageHost implements StageHost { challenge = undefined; flowSlug = undefined; loading = false; - tenant = undefined; + brand = undefined; async submit(payload: unknown): Promise { this.promptForm.previewResult = payload; return false; @@ -121,120 +122,35 @@ export class PromptForm extends ModelForm { } renderTypes(): TemplateResult { - return html` - - - - - - - - - - - - - - - - - - - `; + // prettier-ignore + const promptTypesWithLabels = [ + [PromptTypeEnum.Text, msg("Text: Simple Text input")], + [PromptTypeEnum.TextArea, msg("Text Area: Multiline text input")], + [PromptTypeEnum.TextReadOnly, msg("Text (read-only): Simple Text input, but cannot be edited.")], + [PromptTypeEnum.TextAreaReadOnly, msg("Text Area (read-only): Multiline text input, but cannot be edited.")], + [PromptTypeEnum.Username, msg("Username: Same as Text input, but checks for and prevents duplicate usernames.")], + [PromptTypeEnum.Email, msg("Email: Text field with Email type.")], + [PromptTypeEnum.Password, msg("Password: Masked input, multiple inputs of this type on the same prompt need to be identical.")], + [PromptTypeEnum.Number, msg("Number")], + [PromptTypeEnum.Checkbox, msg("Checkbox")], + [PromptTypeEnum.RadioButtonGroup, msg("Radio Button Group (fixed choice)")], + [PromptTypeEnum.Dropdown, msg("Dropdown (fixed choice)")], + [PromptTypeEnum.Date, msg("Date")], + [PromptTypeEnum.DateTime, msg("Date Time")], + [PromptTypeEnum.File, msg("File")], + [PromptTypeEnum.Separator, msg("Separator: Static Separator Line")], + [PromptTypeEnum.Hidden, msg("Hidden: Hidden field, can be used to insert data into form.")], + [PromptTypeEnum.Static, msg("Static: Static value, displayed as-is.")], + [PromptTypeEnum.AkLocale, msg("authentik: Locale: Displays a list of locales authentik supports.")], + ]; + const currentType = this.instance?.type; + return html` ${map( + promptTypesWithLabels, + ([promptType, label]) => + html``, + )}`; } renderForm(): TemplateResult { diff --git a/web/src/admin/stages/prompt/PromptListPage.ts b/web/src/admin/stages/prompt/PromptListPage.ts index c2b84a689b..3b3a7869ee 100644 --- a/web/src/admin/stages/prompt/PromptListPage.ts +++ b/web/src/admin/stages/prompt/PromptListPage.ts @@ -33,6 +33,7 @@ export class PromptListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "name"; diff --git a/web/src/admin/stages/source/SourceStageForm.ts b/web/src/admin/stages/source/SourceStageForm.ts new file mode 100644 index 0000000000..bfb4d5e40a --- /dev/null +++ b/web/src/admin/stages/source/SourceStageForm.ts @@ -0,0 +1,103 @@ +import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import "@goauthentik/elements/forms/HorizontalFormElement"; +import "@goauthentik/elements/forms/SearchSelect/index"; +import "@goauthentik/elements/utils/TimeDeltaHelp"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +import { + Source, + SourceStage, + SourcesAllListRequest, + SourcesApi, + StagesApi, +} from "@goauthentik/api"; + +@customElement("ak-stage-source-form") +export class SourceStageForm extends BaseStageForm { + loadInstance(pk: string): Promise { + return new StagesApi(DEFAULT_CONFIG).stagesSourceRetrieve({ + stageUuid: pk, + }); + } + + async send(data: SourceStage): Promise { + if (this.instance) { + return new StagesApi(DEFAULT_CONFIG).stagesSourceUpdate({ + stageUuid: this.instance.pk || "", + sourceStageRequest: data, + }); + } else { + return new StagesApi(DEFAULT_CONFIG).stagesSourceCreate({ + sourceStageRequest: data, + }); + } + } + + renderForm(): TemplateResult { + return html` + ${msg( + "Inject an OAuth or SAML Source into the flow execution. This allows for additional user verification, or to dynamically access different sources for different user identifiers (username, email address, etc).", + )} + + + + + => { + const args: SourcesAllListRequest = { + ordering: "name", + }; + if (query !== undefined) { + args.search = query; + } + const users = await new SourcesApi(DEFAULT_CONFIG).sourcesAllList(args); + return users.results; + }} + .renderElement=${(source: Source): string => { + return source.name; + }} + .renderDescription=${(source: Source): TemplateResult => { + return html`${source.verboseName}`; + }} + .value=${(source: Source | undefined): string | undefined => { + return source?.pk; + }} + .selected=${(source: Source): boolean => { + return source.pk === this.instance?.source; + }} + > + + + + +

      + ${msg( + "Amount of time a user can take to return from the source to continue the flow.", + )} +

      + +
      + `; + } +} diff --git a/web/src/admin/stages/user_login/UserLoginStageForm.ts b/web/src/admin/stages/user_login/UserLoginStageForm.ts index c386a9af2a..e7c94c247b 100644 --- a/web/src/admin/stages/user_login/UserLoginStageForm.ts +++ b/web/src/admin/stages/user_login/UserLoginStageForm.ts @@ -10,7 +10,7 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; -import { StagesApi, UserLoginStage } from "@goauthentik/api"; +import { GeoipBindingEnum, NetworkBindingEnum, StagesApi, UserLoginStage } from "@goauthentik/api"; @customElement("ak-stage-user-login-form") export class UserLoginStageForm extends BaseStageForm { @@ -93,6 +93,74 @@ export class UserLoginStageForm extends BaseStageForm {

      + + + +

      + ${msg( + "Configure if sessions created by this stage should be bound to the Networks they were created in.", + )} +

      +
      + + + +

      + ${msg( + "Configure if sessions created by this stage should be bound to their GeoIP-based location", + )} +

      +
      - + { .fetchObjects=${async (query?: string): Promise => { const args: CoreGroupsListRequest = { ordering: "name", + includeUsers: false, }; if (query !== undefined) { args.search = query; diff --git a/web/src/admin/system-tasks/SystemTaskListPage.ts b/web/src/admin/system-tasks/SystemTaskListPage.ts index f180f63cc0..9947bc0d7c 100644 --- a/web/src/admin/system-tasks/SystemTaskListPage.ts +++ b/web/src/admin/system-tasks/SystemTaskListPage.ts @@ -1,8 +1,11 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/SpinnerButton"; +import "@goauthentik/elements/events/LogViewer"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -14,13 +17,10 @@ import { customElement, property } from "lit/decorators.js"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; -import { AdminApi, Task, TaskStatusEnum } from "@goauthentik/api"; +import { EventsApi, SystemTask, SystemTaskStatusEnum } from "@goauthentik/api"; @customElement("ak-system-task-list") -export class SystemTaskListPage extends TablePage { - searchEnabled(): boolean { - return false; - } +export class SystemTaskListPage extends TablePage { pageTitle(): string { return msg("System Tasks"); } @@ -33,54 +33,50 @@ export class SystemTaskListPage extends TablePage { expandable = true; + searchEnabled(): boolean { + return true; + } + @property() - order = "slug"; + order = "name"; static get styles(): CSSResult[] { return super.styles.concat(PFDescriptionList); } - async apiEndpoint(page: number): Promise> { - return new AdminApi(DEFAULT_CONFIG).adminSystemTasksList().then((tasks) => { - return { - pagination: { - count: tasks.length, - totalPages: 1, - startIndex: 1, - endIndex: tasks.length, - current: page, - next: 0, - previous: 0, - }, - results: tasks, - }; + async apiEndpoint(page: number): Promise> { + return new EventsApi(DEFAULT_CONFIG).eventsSystemTasksList({ + ordering: this.order, + page: page, + pageSize: (await uiConfig()).pagination.perPage, + search: this.search || "", }); } columns(): TableColumn[] { return [ - new TableColumn(msg("Identifier")), + new TableColumn(msg("Identifier"), "name"), new TableColumn(msg("Description")), new TableColumn(msg("Last run")), - new TableColumn(msg("Status")), + new TableColumn(msg("Status"), "status"), new TableColumn(msg("Actions")), ]; } - taskStatus(task: Task): TemplateResult { + taskStatus(task: SystemTask): TemplateResult { switch (task.status) { - case TaskStatusEnum.Successful: + case SystemTaskStatusEnum.Successful: return html`${msg("Successful")}`; - case TaskStatusEnum.Warning: + case SystemTaskStatusEnum.Warning: return html`${msg("Warning")}`; - case TaskStatusEnum.Error: + case SystemTaskStatusEnum.Error: return html`${msg("Error")}`; default: return html`${msg("Unknown")}`; } } - renderExpanded(item: Task): TemplateResult { + renderExpanded(item: SystemTask): TemplateResult { return html`
      @@ -90,7 +86,28 @@ export class SystemTaskListPage extends TablePage {
      - ${msg(str`${item.taskDuration.toFixed(2)} seconds`)} + ${msg(str`${item.duration.toFixed(2)} seconds`)} +
      +
      +
      +
      +
      + ${msg("Expiry")} +
      +
      +
      + ${item.expiring + ? html` + + ${getRelativeTime(item.expires || new Date())} + + ` + : msg("-")}
      @@ -100,9 +117,7 @@ export class SystemTaskListPage extends TablePage {
      - ${item.messages.map((m) => { - return html`
    • ${m}
    • `; - })} +
      @@ -113,18 +128,19 @@ export class SystemTaskListPage extends TablePage { `; } - row(item: Task): TemplateResult[] { + row(item: SystemTask): TemplateResult[] { return [ - html`${item.taskName}`, - html`${item.taskDescription}`, - html`${item.taskFinishTimestamp.toLocaleString()}`, + html`
      ${item.name}${item.uid ? `:${item.uid}` : ""}
      `, + html`${item.description}`, + html`
      ${getRelativeTime(item.finishTimestamp)}
      + ${item.finishTimestamp.toLocaleString()}`, this.taskStatus(item), html` { - return new AdminApi(DEFAULT_CONFIG) - .adminSystemTasksRetryCreate({ - id: item.taskName, + return new EventsApi(DEFAULT_CONFIG) + .eventsSystemTasksRunCreate({ + uuid: item.uuid, }) .then(() => { this.dispatchEvent( diff --git a/web/src/admin/tokens/TokenListPage.ts b/web/src/admin/tokens/TokenListPage.ts index 953cd17dd5..65a90bcfab 100644 --- a/web/src/admin/tokens/TokenListPage.ts +++ b/web/src/admin/tokens/TokenListPage.ts @@ -2,6 +2,7 @@ import "@goauthentik/admin/tokens/TokenForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { intentToLabel } from "@goauthentik/common/labels"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/TokenCopyButton"; @@ -42,6 +43,7 @@ export class TokenListPage extends TablePage { } checkbox = true; + clearOnRefresh = true; @property() order = "expires"; @@ -110,7 +112,10 @@ export class TokenListPage extends TablePage { : html``}`, html`${item.userObj?.username}`, html``, - html`${item.expiring ? item.expires?.toLocaleString() : msg("-")}`, + html`${item.expires && item.expiring + ? html`
      ${getRelativeTime(item.expires)}
      + ${item.expires.toLocaleString()}` + : msg("-")}`, html`${intentToLabel(item.intent ?? IntentEnum.Api)}`, html` ${!item.managed diff --git a/web/src/admin/users/GroupSelectModal.ts b/web/src/admin/users/GroupSelectModal.ts index eac99d4aeb..015c8658fa 100644 --- a/web/src/admin/users/GroupSelectModal.ts +++ b/web/src/admin/users/GroupSelectModal.ts @@ -38,6 +38,7 @@ export class GroupSelectModal extends TableModal { page: page, pageSize: (await uiConfig()).pagination.perPage, search: this.search || "", + includeUsers: false, }); } diff --git a/web/src/admin/users/UserApplicationTable.ts b/web/src/admin/users/UserApplicationTable.ts new file mode 100644 index 0000000000..c5af92624d --- /dev/null +++ b/web/src/admin/users/UserApplicationTable.ts @@ -0,0 +1,79 @@ +import { applicationListStyle } from "@goauthentik/admin/applications/ApplicationListPage"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { PFSize } from "@goauthentik/common/enums.js"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/components/ak-app-icon"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { Application, CoreApi, User } from "@goauthentik/api"; + +@customElement("ak-user-application-table") +export class UserApplicationTable extends Table { + @property({ attribute: false }) + user?: User; + + static get styles(): CSSResult[] { + return super.styles.concat(applicationListStyle); + } + + async apiEndpoint(page: number): Promise> { + return new CoreApi(DEFAULT_CONFIG).coreApplicationsList({ + forUser: this.user?.pk, + page: page, + pageSize: (await uiConfig()).pagination.perPage, + ordering: this.order, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn(""), + new TableColumn(msg("Name"), "name"), + new TableColumn(msg("Group"), "group"), + new TableColumn(msg("Provider")), + new TableColumn(msg("Provider Type")), + new TableColumn(msg("Actions")), + ]; + } + + row(item: Application): TemplateResult[] { + return [ + html``, + html` +
      ${item.name}
      + ${item.metaPublisher ? html`${item.metaPublisher}` : html``} +
      `, + html`${item.group || msg("-")}`, + item.provider + ? html` + ${item.providerObj?.name} + ` + : html`-`, + html`${item.providerObj?.verboseName || msg("-")}`, + html` + ${msg("Update")} + ${msg("Update Application")} + + + + + ${item.launchUrl + ? html` + + + + ` + : html``}`, + ]; + } +} diff --git a/web/src/admin/users/UserAssignedGlobalPermissionsTable.ts b/web/src/admin/users/UserAssignedGlobalPermissionsTable.ts index 99f171c816..e1fdf58ec8 100644 --- a/web/src/admin/users/UserAssignedGlobalPermissionsTable.ts +++ b/web/src/admin/users/UserAssignedGlobalPermissionsTable.ts @@ -1,9 +1,9 @@ import "@goauthentik/admin/users/UserPermissionForm"; -import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; -import { groupBy } from "@goauthentik/app/common/utils"; -import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { groupBy } from "@goauthentik/common/utils"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; @@ -19,6 +19,7 @@ export class UserAssignedGlobalPermissionsTable extends Table { userId?: number; checkbox = true; + clearOnRefresh = true; apiEndpoint(page: number): Promise> { return new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({ @@ -83,6 +84,10 @@ export class UserAssignedGlobalPermissionsTable extends Table { } row(item: Permission): TemplateResult[] { - return [html`${item.modelVerbose}`, html`${item.name}`, html`✓`]; + return [ + html`${item.modelVerbose}`, + html`${item.name}`, + html``, + ]; } } diff --git a/web/src/admin/users/UserAssignedObjectPermissionsTable.ts b/web/src/admin/users/UserAssignedObjectPermissionsTable.ts index 8e63ae8bb2..2137fc09ab 100644 --- a/web/src/admin/users/UserAssignedObjectPermissionsTable.ts +++ b/web/src/admin/users/UserAssignedObjectPermissionsTable.ts @@ -1,7 +1,7 @@ -import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; -import { groupBy } from "@goauthentik/app/common/utils"; -import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { groupBy } from "@goauthentik/common/utils"; import "@goauthentik/elements/forms/DeleteBulkForm"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; @@ -16,6 +16,7 @@ export class UserAssignedObjectPermissionsTable extends Table> { return new RbacApi(DEFAULT_CONFIG).rbacPermissionsUsersList({ @@ -85,7 +86,7 @@ export class UserAssignedObjectPermissionsTable extends Table
      ${item.objectPk}
      `}`, - html`✓`, + html``, ]; } } diff --git a/web/src/admin/users/UserDevicesTable.ts b/web/src/admin/users/UserDevicesTable.ts index 06ead21dfd..9f02e7fcd6 100644 --- a/web/src/admin/users/UserDevicesTable.ts +++ b/web/src/admin/users/UserDevicesTable.ts @@ -16,6 +16,7 @@ export class UserDeviceTable extends Table { userId?: number; checkbox = true; + clearOnRefresh = true; async apiEndpoint(): Promise> { return new AuthenticatorsApi(DEFAULT_CONFIG) diff --git a/web/src/admin/users/UserForm.ts b/web/src/admin/users/UserForm.ts index 061fd6f56a..f8b0b00e39 100644 --- a/web/src/admin/users/UserForm.ts +++ b/web/src/admin/users/UserForm.ts @@ -128,6 +128,14 @@ export class UserForm extends ModelForm { "Service accounts should be used for machine-to-machine authentication or other automations.", )}`, }, + { + label: "Internal Service account", + value: UserTypeEnum.InternalServiceAccount, + disabled: true, + description: html`${msg( + "Internal Service accounts are created and managed by authentik and cannot be created manually.", + )}`, + }, ]} .value=${this.instance?.type} > diff --git a/web/src/admin/users/UserListPage.ts b/web/src/admin/users/UserListPage.ts index 1d861b568d..4dfa67f4b8 100644 --- a/web/src/admin/users/UserListPage.ts +++ b/web/src/admin/users/UserListPage.ts @@ -4,15 +4,20 @@ import "@goauthentik/admin/users/UserActiveForm"; import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserPasswordForm"; import "@goauthentik/admin/users/UserResetEmailForm"; -import { me } from "@goauthentik/app/common/users"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { PFSize } from "@goauthentik/common/enums.js"; import { userTypeToLabel } from "@goauthentik/common/labels"; import { MessageLevel } from "@goauthentik/common/messages"; import { DefaultUIConfig, uiConfig } from "@goauthentik/common/ui/config"; -import { first } from "@goauthentik/common/utils"; +import { me } from "@goauthentik/common/users"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import { rootInterface } from "@goauthentik/elements/Base"; -import { PFSize } from "@goauthentik/elements/Spinner"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/TreeView"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; @@ -33,18 +38,11 @@ import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; -import { - CapabilitiesEnum, - CoreApi, - ResponseError, - SessionUser, - User, - UserPath, -} from "@goauthentik/api"; +import { CoreApi, ResponseError, SessionUser, User, UserPath } from "@goauthentik/api"; export const requestRecoveryLink = (user: User) => new CoreApi(DEFAULT_CONFIG) - .coreUsersRecoveryRetrieve({ + .coreUsersRecoveryCreate({ id: user.pk, }) .then((rec) => @@ -63,7 +61,7 @@ export const requestRecoveryLink = (user: User) => showMessage({ level: MessageLevel.error, message: msg( - "The current tenant must have a recovery flow configured to use a recovery link", + "The current brand must have a recovery flow configured to use a recovery link", ), }), ), @@ -93,9 +91,10 @@ const recoveryButtonStyles = css` `; @customElement("ak-user-list") -export class UserListPage extends TablePage { +export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePage)) { expandable = true; checkbox = true; + clearOnRefresh = true; searchEnabled(): boolean { return true; @@ -147,6 +146,7 @@ export class UserListPage extends TablePage { search: this.search || "", pathStartswith: getURLParam("path", ""), isActive: this.hideDeactivated ? true : undefined, + includeGroups: false, }); this.userPaths = await new CoreApi(DEFAULT_CONFIG).coreUsersPathsRetrieve({ search: this.search, @@ -160,6 +160,7 @@ export class UserListPage extends TablePage { new TableColumn(msg("Name"), "username"), new TableColumn(msg("Active"), "is_active"), new TableColumn(msg("Last login"), "last_login"), + new TableColumn(msg("Type"), "type"), new TableColumn(msg("Actions")), ]; } @@ -244,15 +245,18 @@ export class UserListPage extends TablePage { row(item: User): TemplateResult[] { const canImpersonate = - rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && - item.pk !== this.me?.user.pk; + this.can(CapabilitiesEnum.CanImpersonate) && item.pk !== this.me?.user.pk; return [ html` -
      ${item.username}
      - ${item.name === "" ? msg("") : item.name}
       ${userTypeToLabel(item.type)}`, +
      ${item.username}
      + ${item.name === "" ? msg("") : item.name} + `, html``, - html`${first(item.lastLogin?.toLocaleString(), msg("-"))}`, + html`${item.lastLogin + ? html`
      ${getRelativeTime(item.lastLogin)}
      + ${item.lastLogin.toLocaleString()}` + : msg("-")}`, + html`${userTypeToLabel(item.type)}`, html` ${msg("Update")} ${msg("Update User")} @@ -355,7 +359,7 @@ export class UserListPage extends TablePage { ${msg("Set password")} - ${rootInterface()?.tenant?.flowRecovery + ${this.brand.flowRecovery ? html` { ` : html`

      ${msg( - "To let a user directly reset a their password, configure a recovery flow on the currently active tenant.", + "To let a user directly reset a their password, configure a recovery flow on the currently active brand.", )}

      `} diff --git a/web/src/admin/users/UserResetEmailForm.ts b/web/src/admin/users/UserResetEmailForm.ts index bd450a57bd..a11f17d440 100644 --- a/web/src/admin/users/UserResetEmailForm.ts +++ b/web/src/admin/users/UserResetEmailForm.ts @@ -10,7 +10,7 @@ import { customElement, property } from "lit/decorators.js"; import { CoreApi, - CoreUsersRecoveryEmailRetrieveRequest, + CoreUsersRecoveryEmailCreateRequest, Stage, StagesAllListRequest, StagesApi, @@ -18,7 +18,7 @@ import { } from "@goauthentik/api"; @customElement("ak-user-reset-email-form") -export class UserResetEmailForm extends Form { +export class UserResetEmailForm extends Form { @property({ attribute: false }) user!: User; @@ -26,9 +26,9 @@ export class UserResetEmailForm extends Form { + async send(data: CoreUsersRecoveryEmailCreateRequest): Promise { data.id = this.user.pk; - return new CoreApi(DEFAULT_CONFIG).coreUsersRecoveryEmailRetrieve(data); + return new CoreApi(DEFAULT_CONFIG).coreUsersRecoveryEmailCreate(data); } renderForm(): TemplateResult { diff --git a/web/src/admin/users/UserViewPage.ts b/web/src/admin/users/UserViewPage.ts index 6ef77aa51c..f5188d12be 100644 --- a/web/src/admin/users/UserViewPage.ts +++ b/web/src/admin/users/UserViewPage.ts @@ -1,19 +1,20 @@ import "@goauthentik/admin/groups/RelatedGroupList"; +import "@goauthentik/admin/providers/rac/ConnectionTokenList"; import "@goauthentik/admin/users/UserActiveForm"; +import "@goauthentik/admin/users/UserApplicationTable"; import "@goauthentik/admin/users/UserChart"; import "@goauthentik/admin/users/UserForm"; -import "@goauthentik/admin/users/UserPasswordForm"; -import "@goauthentik/app/admin/users/UserAssignedGlobalPermissionsTable"; -import "@goauthentik/app/admin/users/UserAssignedObjectPermissionsTable"; import { renderRecoveryEmailRequest, requestRecoveryLink, -} from "@goauthentik/app/admin/users/UserListPage"; -import { me } from "@goauthentik/app/common/users"; -import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; +} from "@goauthentik/admin/users/UserListPage"; +import "@goauthentik/admin/users/UserPasswordForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { PFSize } from "@goauthentik/common/enums.js"; import { userTypeToLabel } from "@goauthentik/common/labels"; +import { me } from "@goauthentik/common/users"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/DescriptionList"; import { type DescriptionPair, @@ -22,20 +23,23 @@ import { import "@goauthentik/components/ak-status-label"; import "@goauthentik/components/events/ObjectChangelog"; import "@goauthentik/components/events/UserEvents"; -import { AKElement, rootInterface } from "@goauthentik/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/CodeMirror"; +import { WithCapabilitiesConfig } from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/PageHeader"; -import { PFSize } from "@goauthentik/elements/Spinner"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/ModalForm"; -import "@goauthentik/elements/oauth/UserRefreshList"; +import "@goauthentik/elements/oauth/UserAccessTokenList"; +import "@goauthentik/elements/oauth/UserRefreshTokenList"; +import "@goauthentik/elements/rbac/ObjectPermissionsPage"; import "@goauthentik/elements/user/SessionList"; import "@goauthentik/elements/user/UserConsentList"; +import "@goauthentik/elements/user/sources/SourceSettings"; import { msg, str } from "@lit/localize"; -import { css, html, nothing } from "lit"; +import { TemplateResult, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; @@ -60,7 +64,7 @@ import { import "./UserDevicesTable"; @customElement("ak-user-view") -export class UserViewPage extends AKElement { +export class UserViewPage extends WithCapabilitiesConfig(AKElement) { @property({ type: Number }) set userId(id: number) { me().then((me) => { @@ -147,7 +151,10 @@ export class UserViewPage extends AKElement { [msg("Username"), user.username], [msg("Name"), user.name], [msg("Email"), user.email || "-"], - [msg("Last login"), user.lastLogin?.toLocaleString()], + [msg("Last login"), user.lastLogin + ? html`
      ${getRelativeTime(user.lastLogin)}
      + ${user.lastLogin.toLocaleString()}` + : html`${msg("-")}`], [msg("Active"), html``], [msg("Type"), userTypeToLabel(user.type)], [msg("Superuser"), html``], @@ -163,8 +170,7 @@ export class UserViewPage extends AKElement { renderActionButtons(user: User) { const canImpersonate = - rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) && - user.pk !== this.me?.user.pk; + this.can(CapabilitiesEnum.CanImpersonate) && user.pk !== this.me?.user.pk; return html`
      @@ -227,48 +233,125 @@ export class UserViewPage extends AKElement { renderRecoveryButtons(user: User) { return html`
      - - ${msg("Update password")} - ${msg("Update password")} - - - - requestRecoveryLink(user)} - > - - ${msg("Create Recovery Link")} - - - ${user.email ? renderRecoveryEmailRequest(user) : nothing} -
      -
      + + ${msg("Update password")} + ${msg("Update password")} + + + + requestRecoveryLink(user)} + > + + ${msg("Create Recovery Link")} + + + ${user.email ? renderRecoveryEmailRequest(user) : nothing} + `; + } + + renderTabCredentialsToken(user: User): TemplateResult { + return html` + +
      +
      +
      + + +
      - - +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      + + +
      +
      +
      +
      +
      +
      + + +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      + + +
      +
      +
      +
      + + +
      +
      +
      `; } + renderTabApplications(user: User): TemplateResult { + return html`
      +
      + +
      +
      `; + } + renderBody() { if (!this.user) { return nothing; @@ -326,18 +409,6 @@ export class UserViewPage extends AKElement { -
      -
      -
      - - -
      -
      -
      -
      -
      -
      - -
      -
      +
      + ${this.renderTabCredentialsToken(this.user)}
      -
      -
      - - -
      -
      -
      -
      -
      -
      - -
      -
      + ${this.renderTabApplications(this.user)}
      -
      -
      - ${msg("RBAC is in preview.")} - ${msg("Send us feedback!")} -
      -
      -
      -
      -
      - ${msg("Assigned global permissions")} -
      -
      - - -
      -
      -
      -
      - ${msg("Assigned object permissions")} -
      -
      - - -
      -
      -
      -
      -
      + `; } } diff --git a/web/src/assets/images/flow_background.jpg b/web/src/assets/images/flow_background.jpg index ae78b49e99..643337c052 100644 Binary files a/web/src/assets/images/flow_background.jpg and b/web/src/assets/images/flow_background.jpg differ diff --git a/web/src/common/api/config.ts b/web/src/common/api/config.ts index 428a80c233..ec7023bf50 100644 --- a/web/src/common/api/config.ts +++ b/web/src/common/api/config.ts @@ -1,13 +1,13 @@ -import { TokenMiddleware } from "@goauthentik/app/common/oauth/middleware"; import { CSRFMiddleware, EventMiddleware, LoggingMiddleware, } from "@goauthentik/common/api/middleware"; -import { EVENT_LOCALE_REQUEST, EVENT_REFRESH, VERSION } from "@goauthentik/common/constants"; +import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants"; import { globalAK } from "@goauthentik/common/global"; +import { TokenMiddleware } from "@goauthentik/common/oauth-middleware.js"; -import { Config, Configuration, CoreApi, CurrentTenant, RootApi } from "@goauthentik/api"; +import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api"; let globalConfigPromise: Promise | undefined = Promise.resolve(globalAK().config); export function config(): Promise { @@ -17,7 +17,7 @@ export function config(): Promise { return globalConfigPromise; } -export function tenantSetFavicon(tenant: CurrentTenant) { +export function brandSetFavicon(brand: CurrentBrand) { /** * * @@ -30,36 +30,36 @@ export function tenantSetFavicon(tenant: CurrentTenant) { relIcon.rel = rel; document.getElementsByTagName("head")[0].appendChild(relIcon); } - relIcon.href = tenant.brandingFavicon; + relIcon.href = brand.brandingFavicon; }); } -export function tenantSetLocale(tenant: CurrentTenant) { - if (tenant.defaultLocale === "") { +export function brandSetLocale(brand: CurrentBrand) { + if (brand.defaultLocale === "") { return; } - console.debug("authentik/locale: setting locale from tenant default"); + console.debug("authentik/locale: setting locale from brand default"); window.dispatchEvent( new CustomEvent(EVENT_LOCALE_REQUEST, { composed: true, bubbles: true, - detail: { locale: tenant.defaultLocale }, - }), + detail: { locale: brand.defaultLocale }, + }) ); } -let globalTenantPromise: Promise | undefined = Promise.resolve(globalAK().tenant); -export function tenant(): Promise { - if (!globalTenantPromise) { - globalTenantPromise = new CoreApi(DEFAULT_CONFIG) - .coreTenantsCurrentRetrieve() - .then((tenant) => { - tenantSetFavicon(tenant); - tenantSetLocale(tenant); - return tenant; +let globalBrandPromise: Promise | undefined = Promise.resolve(globalAK().brand); +export function brand(): Promise { + if (!globalBrandPromise) { + globalBrandPromise = new CoreApi(DEFAULT_CONFIG) + .coreBrandsCurrentRetrieve() + .then((brand) => { + brandSetFavicon(brand); + brandSetLocale(brand); + return brand; }); } - return globalTenantPromise; + return globalBrandPromise; } export function getMetaContent(key: string): string { @@ -77,7 +77,7 @@ export const DEFAULT_CONFIG = new Configuration({ new TokenMiddleware(), new CSRFMiddleware(), new EventMiddleware(), - new LoggingMiddleware(globalAK().tenant), + new LoggingMiddleware(globalAK().brand), ], }); @@ -88,13 +88,4 @@ export function AndNext(url: string): string { return `?next=${encodeURIComponent(url)}`; } -window.addEventListener(EVENT_REFRESH, () => { - // Upon global refresh, disregard whatever was pre-hydrated and - // actually load info from API - globalConfigPromise = undefined; - globalTenantPromise = undefined; - config(); - tenant(); -}); - console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`); diff --git a/web/src/common/api/middleware.ts b/web/src/common/api/middleware.ts index 745a5d1c92..8aee822c7a 100644 --- a/web/src/common/api/middleware.ts +++ b/web/src/common/api/middleware.ts @@ -2,7 +2,7 @@ import { EVENT_REQUEST_POST } from "@goauthentik/common/constants"; import { getCookie } from "@goauthentik/common/utils"; import { - CurrentTenant, + CurrentBrand, FetchParams, Middleware, RequestContext, @@ -18,13 +18,13 @@ export interface RequestInfo { } export class LoggingMiddleware implements Middleware { - tenant: CurrentTenant; - constructor(tenant: CurrentTenant) { - this.tenant = tenant; + brand: CurrentBrand; + constructor(brand: CurrentBrand) { + this.brand = brand; } post(context: ResponseContext): Promise { - let msg = `authentik/api[${this.tenant.matchedDomain}]: `; + let msg = `authentik/api[${this.brand.matchedDomain}]: `; // https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output msg += `%c${context.response.status}%c ${context.init.method} ${context.url}`; let style = ""; diff --git a/web/src/common/constants.ts b/web/src/common/constants.ts index 459fc91a51..8d8a1631bf 100644 --- a/web/src/common/constants.ts +++ b/web/src/common/constants.ts @@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; export const ERROR_CLASS = "pf-m-danger"; export const PROGRESS_CLASS = "pf-m-in-progress"; export const CURRENT_CLASS = "pf-m-current"; -export const VERSION = "2023.10.4"; +export const VERSION = "2024.4.2"; export const TITLE_DEFAULT = "authentik"; export const ROUTE_SEPARATOR = ";"; @@ -19,6 +19,7 @@ export const EVENT_LOCALE_REQUEST = "ak-locale-request"; export const EVENT_REQUEST_POST = "ak-request-post"; export const EVENT_MESSAGE = "ak-message"; export const EVENT_THEME_CHANGE = "ak-theme-change"; +export const EVENT_REFRESH_ENTERPRISE = "ak-refresh-enterprise"; export const WS_MSG_TYPE_MESSAGE = "message"; export const WS_MSG_TYPE_REFRESH = "refresh"; diff --git a/web/src/common/enums.ts b/web/src/common/enums.ts new file mode 100644 index 0000000000..5087054228 --- /dev/null +++ b/web/src/common/enums.ts @@ -0,0 +1,6 @@ +export enum PFSize { + Small = "pf-m-sm", + Medium = "pf-m-md", + Large = "pf-m-lg", + XLarge = "pf-m-xl", +} diff --git a/web/src/common/errors.ts b/web/src/common/errors.ts index ad6156bfad..6d71b27704 100644 --- a/web/src/common/errors.ts +++ b/web/src/common/errors.ts @@ -16,7 +16,7 @@ export async function parseAPIError(error: Error): Promise { if (!(error instanceof ResponseError)) { return error; } - if (error.response.status < 400 && error.response.status > 499) { + if (error.response.status < 400 || error.response.status > 499) { return error; } const body = await error.response.json(); diff --git a/web/src/common/events.ts b/web/src/common/events.ts index b8af9f6751..aedad64d97 100644 --- a/web/src/common/events.ts +++ b/web/src/common/events.ts @@ -5,6 +5,7 @@ export interface EventUser { email?: string; username: string; on_behalf_of?: EventUser; + is_anonymous?: boolean; } export interface EventContext { diff --git a/web/src/common/global.ts b/web/src/common/global.ts index d9a27e7f09..990303df0d 100644 --- a/web/src/common/global.ts +++ b/web/src/common/global.ts @@ -1,4 +1,4 @@ -import { Config, ConfigFromJSON, CurrentTenant, CurrentTenantFromJSON } from "@goauthentik/api"; +import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api"; export interface GlobalAuthentik { _converted?: boolean; @@ -7,7 +7,7 @@ export interface GlobalAuthentik { layout: string; }; config: Config; - tenant: CurrentTenant; + brand: CurrentBrand; versionFamily: string; versionSubdomain: string; build: string; @@ -21,7 +21,7 @@ export function globalAK(): GlobalAuthentik { const ak = (window as unknown as AuthentikWindow).authentik; if (ak && !ak._converted) { ak._converted = true; - ak.tenant = CurrentTenantFromJSON(ak.tenant); + ak.brand = CurrentBrandFromJSON(ak.brand); ak.config = ConfigFromJSON(ak.config); } if (!ak) { @@ -29,7 +29,7 @@ export function globalAK(): GlobalAuthentik { config: ConfigFromJSON({ capabilities: [], }), - tenant: CurrentTenantFromJSON({ + brand: CurrentBrandFromJSON({ ui_footer_links: [], }), versionFamily: "", diff --git a/web/src/common/helpers/plex.ts b/web/src/common/helpers/plex.ts index 6e9639934b..c3735af5bd 100644 --- a/web/src/common/helpers/plex.ts +++ b/web/src/common/helpers/plex.ts @@ -55,9 +55,7 @@ export class PlexAPIClient { ): Promise<{ authUrl: string; pin: PlexPinResponse }> { const headers = { ...DEFAULT_HEADERS, - ...{ - "X-Plex-Client-Identifier": clientIdentifier, - }, + "X-Plex-Client-Identifier": clientIdentifier, }; const pinResponse = await fetch("https://plex.tv/api/v2/pins.json?strong=true", { method: "POST", @@ -75,9 +73,7 @@ export class PlexAPIClient { static async pinStatus(clientIdentifier: string, id: number): Promise { const headers = { ...DEFAULT_HEADERS, - ...{ - "X-Plex-Client-Identifier": clientIdentifier, - }, + "X-Plex-Client-Identifier": clientIdentifier, }; const pinResponse = await fetch(`https://plex.tv/api/v2/pins/${id}`, { headers: headers, diff --git a/web/src/common/helpers/webauthn.ts b/web/src/common/helpers/webauthn.ts index c10edff170..13a9894503 100644 --- a/web/src/common/helpers/webauthn.ts +++ b/web/src/common/helpers/webauthn.ts @@ -38,7 +38,7 @@ export function transformCredentialCreateOptions( // Because json can't contain raw bytes, the server base64-encodes the User ID // So to get the base64 encoded byte array, we first need to convert it to a regular // string, then a byte array, re-encode it and wrap that in an array. - const stringId = decodeURIComponent(escape(window.atob(userId))); + const stringId = decodeURIComponent(window.atob(userId)); user.id = u8arr(b64enc(u8arr(stringId))); const challenge = u8arr(credentialCreateOptions.challenge.toString()); diff --git a/web/src/common/oauth/middleware.ts b/web/src/common/oauth-middleware.ts similarity index 89% rename from web/src/common/oauth/middleware.ts rename to web/src/common/oauth-middleware.ts index 43c62b1be9..a1a4b46658 100644 --- a/web/src/common/oauth/middleware.ts +++ b/web/src/common/oauth-middleware.ts @@ -1,4 +1,4 @@ -import { adminSettings } from "@goauthentik/app/common/oauth/settings"; +import { adminSettings } from "@goauthentik/components/oauth/settings"; import { UserManager } from "oidc-client-ts"; import { FetchParams, Middleware, RequestContext } from "@goauthentik/api"; diff --git a/web/src/common/oauth/interface.ts b/web/src/common/oauth/interface.ts deleted file mode 100644 index 9eb97a917b..0000000000 --- a/web/src/common/oauth/interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { state } from "@goauthentik/app/common/oauth/constants"; -import { Interface } from "@goauthentik/app/elements/Base"; -import { UserManager, UserManagerSettings } from "oidc-client-ts"; - -export abstract class OAuthInterface extends Interface { - abstract get oauthSettings(): UserManagerSettings; - - private async ensureLoggedIn() { - const client = new UserManager(this.oauthSettings); - const user = await client.getUser(); - if (user !== null) { - return; - } - if (window.location.href.startsWith(this.oauthSettings.redirect_uri)) { - return; - } - const s = new state(); - await client.signinRedirect({ - state: s, - }); - } - - async firstUpdated(_changedProperties: Map): Promise { - await this.ensureLoggedIn(); - await super.firstUpdated(_changedProperties); - } -} diff --git a/web/src/common/sentry.ts b/web/src/common/sentry.ts index f89a35f130..0778cbce66 100644 --- a/web/src/common/sentry.ts +++ b/web/src/common/sentry.ts @@ -2,10 +2,16 @@ import { config } from "@goauthentik/common/api/config"; import { VERSION } from "@goauthentik/common/constants"; import { SentryIgnoredError } from "@goauthentik/common/errors"; import { me } from "@goauthentik/common/users"; -import * as Sentry from "@sentry/browser"; -import { Integrations } from "@sentry/tracing"; +import { + ErrorEvent, + EventHint, + browserTracingIntegration, + init, + setTag, + setUser, +} from "@sentry/browser"; -import { Config, ResponseError } from "@goauthentik/api"; +import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api"; export const TAG_SENTRY_COMPONENT = "authentik.component"; export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities"; @@ -13,7 +19,7 @@ export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities"; export async function configureSentry(canDoPpi = false): Promise { const cfg = await config(); if (cfg.errorReporting.enabled) { - Sentry.init({ + init({ dsn: cfg.errorReporting.sentryDsn, ignoreErrors: [ /network/gi, @@ -28,16 +34,18 @@ export async function configureSentry(canDoPpi = false): Promise { ], release: `authentik@${VERSION}`, integrations: [ - new Integrations.BrowserTracing({ - tracingOrigins: [window.location.host, "localhost"], + browserTracingIntegration({ + shouldCreateSpanForRequest: (url: string) => { + return url.startsWith(window.location.host); + }, }), ], tracesSampleRate: cfg.errorReporting.tracesSampleRate, environment: cfg.errorReporting.environment, - beforeSend: async ( - event: Sentry.Event, - hint: Sentry.EventHint | undefined, - ): Promise => { + beforeSend: ( + event: ErrorEvent, + hint: EventHint, + ): ErrorEvent | PromiseLike | null => { if (!hint) { return event; } @@ -53,16 +61,18 @@ export async function configureSentry(canDoPpi = false): Promise { return event; }, }); - Sentry.setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(",")); + setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(",")); if (window.location.pathname.includes("if/")) { - Sentry.setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface()}`); - Sentry.configureScope((scope) => - scope.setTransactionName(`authentik.web.if.${currentInterface()}`), - ); + setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface()}`); + } + if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) { + const Spotlight = await import("@spotlightjs/spotlight"); + + Spotlight.init({ injectImmediately: true }); } if (cfg.errorReporting.sendPii && canDoPpi) { me().then((user) => { - Sentry.setUser({ email: user.user.email }); + setUser({ email: user.user.email }); console.debug("authentik/config: Sentry with PII enabled."); }); } else { diff --git a/web/src/common/styles/authentik.css b/web/src/common/styles/authentik.css index 6a0000e4ef..790c4c4e10 100644 --- a/web/src/common/styles/authentik.css +++ b/web/src/common/styles/authentik.css @@ -124,3 +124,33 @@ html > form > input { margin-right: 6px; margin-bottom: 6px; } + +/* Flow-card adjustments for static pages */ +.pf-c-brand { + padding-top: calc( + var(--pf-c-login__main-footer-links--PaddingTop) + + var(--pf-c-login__main-footer-links--PaddingBottom) + + var(--pf-c-login__main-body--PaddingBottom) + ); + max-height: 9rem; +} + +.ak-brand { + display: flex; + justify-content: center; + width: 100%; +} +.ak-brand img { + padding: 0 2rem; + max-height: inherit; +} + +@media (min-height: 60rem) { + .pf-c-login.stacked .pf-c-login__main { + margin-top: 13rem; + } +} + +.pf-c-data-list { + padding-inline-start: 0; +} diff --git a/web/src/common/styles/theme-dark.css b/web/src/common/styles/theme-dark.css index 530a7c2180..1841ee6135 100644 --- a/web/src/common/styles/theme-dark.css +++ b/web/src/common/styles/theme-dark.css @@ -157,6 +157,10 @@ select[multiple] option:checked { .pf-c-switch__input:checked ~ .pf-c-switch__label { --pf-c-switch__input--checked__label--Color: var(--ak-dark-foreground); } +input[type="datetime-local"]::-webkit-calendar-picker-indicator, +input[type="date"]::-webkit-calendar-picker-indicator { + filter: invert(1); +} /* select toggle */ .pf-c-select__toggle::before { --pf-c-select__toggle--before--BorderTopColor: var(--ak-dark-background-lighter); @@ -183,6 +187,9 @@ select[multiple] option:checked { .pf-c-select__menu-item.pf-m-focus { --pf-c-select__menu-item--focus--BackgroundColor: var(--ak-dark-background-light-ish); } +.pf-c-button:disabled { + color: var(--ak-dark-background-lighter); +} .pf-c-button.pf-m-plain:hover { color: var(--ak-dark-foreground); } @@ -257,7 +264,8 @@ select[multiple] option:checked { .pf-c-login__main-header-desc { color: var(--ak-dark-foreground); } -.pf-c-login__main-footer-links-item img { +.pf-c-login__main-footer-links-item img, +.pf-c-login__main-footer-links-item .fas { filter: invert(1); } .pf-c-login__main-footer-band { @@ -287,6 +295,7 @@ select[multiple] option:checked { } /* data list */ .pf-c-data-list { + padding-inline-start: 0; border-top-color: var(--ak-dark-background-lighter); } .pf-c-data-list__item { @@ -323,3 +332,7 @@ select[multiple] option:checked { .pf-c-tree-view__content:focus-within { --pf-c-tree-view__node--hover--BackgroundColor: var(--ak-dark-background-light-ish); } +/* stepper */ +.pf-c-progress-stepper__step-title { + --pf-c-progress-stepper__step-title--Color: var(--ak-dark-foreground); +} diff --git a/web/src/common/users.ts b/web/src/common/users.ts index 28a75c9ad8..c5fecef580 100644 --- a/web/src/common/users.ts +++ b/web/src/common/users.ts @@ -18,7 +18,7 @@ export function me(): Promise { if (!user.user.settings || !("locale" in user.user.settings)) { return user; } - const locale = user.user.settings.locale; + const locale: string | undefined = user.user.settings.locale; if (locale && locale !== "") { console.debug( `authentik/locale: Activating user's configured locale '${locale}'`, diff --git a/web/src/common/utils.ts b/web/src/common/utils.ts index 2b88f43ddf..7c5dfff92a 100644 --- a/web/src/common/utils.ts +++ b/web/src/common/utils.ts @@ -25,12 +25,6 @@ export function convertToSlug(text: string): string { .replace(/[^\w-]+/g, ""); } -export function convertToTitle(text: string): string { - return text.replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - }); -} - /** * Truncate a string based on maximum word count */ @@ -84,14 +78,6 @@ export function first(...args: Array): T { throw new SentryIgnoredError(`No compatible arg given: ${args}`); } -export function hexEncode(buf: Uint8Array): string { - return Array.from(buf) - .map(function (x) { - return ("0" + x.toString(16)).substr(-2); - }) - .join(""); -} - // Taken from python's string module export const ascii_lowercase = "abcdefghijklmnopqrstuvwxyz"; export const ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; @@ -125,6 +111,21 @@ export function dateTimeLocal(date: Date): string { return `${parts[0]}:${parts[1]}`; } +export function dateToUTC(date: Date): Date { + // Sigh...so our API is UTC/can take TZ info in the ISO format as it should. + // datetime-local fields (which is almost the only date-time input we use) + // can return its value as a UTC timestamp...however the generated API client + // _requires_ a Date object, only to then convert it to an ISO string anyways + // JS Dates don't include timezone info in the ISO string, so that just sends + // the local time as UTC...which is wrong + // Instead we have to do this, convert the given date to a UTC timestamp, + // then subtract the timezone offset to create an "invalid" date (correct time&date) + // but it still "thinks" it's in local TZ + const timestamp = date.getTime(); + const offset = -1 * (new Date().getTimezoneOffset() * 60000); + return new Date(timestamp - offset); +} + // Lit is extremely well-typed with regard to CSS, and Storybook's `build` does not currently have a // coherent way of importing CSS-as-text into CSSStyleSheet. It works well when Storybook is running // in `dev,` but in `build` it fails. Storied components will have to map their textual CSS imports @@ -137,7 +138,7 @@ const isCSSResult = (v: unknown): v is CSSResult => // prettier-ignore export const _adaptCSS = (sheet: AdaptableStylesheet): CSSStyleSheet => - (typeof sheet === "string" ? css([sheet] as unknown as TemplateStringsArray, ...[]).styleSheet + (typeof sheet === "string" ? css([sheet] as unknown as TemplateStringsArray, []).styleSheet : isCSSResult(sheet) ? sheet.styleSheet : sheet) as CSSStyleSheet; @@ -149,3 +150,25 @@ export function adaptCSS(sheet: AdaptableStylesheet[]): CSSStyleSheet[]; export function adaptCSS(sheet: AdaptableStylesheet | AdaptableStylesheet[]): AdaptedStylesheets { return Array.isArray(sheet) ? sheet.map(_adaptCSS) : _adaptCSS(sheet); } + +const _timeUnits = new Map([ + ["year", 24 * 60 * 60 * 1000 * 365], + ["month", (24 * 60 * 60 * 1000 * 365) / 12], + ["day", 24 * 60 * 60 * 1000], + ["hour", 60 * 60 * 1000], + ["minute", 60 * 1000], + ["second", 1000], +]); + +export function getRelativeTime(d1: Date, d2: Date = new Date()): string { + const rtf = new Intl.RelativeTimeFormat("default", { numeric: "auto" }); + const elapsed = d1.getTime() - d2.getTime(); + + // "Math.abs" accounts for both "past" & "future" scenarios + for (const [key, value] of _timeUnits) { + if (Math.abs(elapsed) > value || key == "second") { + return rtf.format(Math.round(elapsed / value), key); + } + } + return rtf.format(Math.round(elapsed / 1000), "second"); +} diff --git a/web/src/components/DescriptionList.ts b/web/src/components/DescriptionList.ts index cfeee4640f..31dfc1416d 100644 --- a/web/src/components/DescriptionList.ts +++ b/web/src/components/DescriptionList.ts @@ -1,3 +1,5 @@ +import { first } from "@goauthentik/common/utils"; + import { TemplateResult, html, nothing } from "lit"; import { classMap } from "lit/directives/class-map.js"; import { map } from "lit/directives/map.js"; @@ -7,10 +9,10 @@ export type DescriptionPair = [string, DescriptionDesc]; export type DescriptionRecord = { term: string; desc: DescriptionDesc }; interface DescriptionConfig { - horizontal: boolean; - compact: boolean; - twocolumn: boolean; - threecolumn: boolean; + horizontal?: boolean; + compact?: boolean; + twocolumn?: boolean; + threecolumn?: boolean; } const isDescriptionRecordCollection = (v: Array): v is DescriptionRecord[] => @@ -78,10 +80,10 @@ export function renderDescriptionList( ) { const checkedTerms = alignTermType(terms); const classes = classMap({ - "pf-m-horizontal": config.horizontal, - "pf-m-compact": config.compact, - "pf-m-2-col-on-lg": config.twocolumn, - "pf-m-3-col-on-lg": config.threecolumn, + "pf-m-horizontal": first(config.horizontal, false), + "pf-m-compact": first(config.compact, false), + "pf-m-2-col-on-lg": first(config.twocolumn, false), + "pf-m-3-col-on-lg": first(config.threecolumn, false), }); return html` diff --git a/web/src/components/ak-app-icon.ts b/web/src/components/ak-app-icon.ts index fcdb9d8930..7237ffc914 100644 --- a/web/src/components/ak-app-icon.ts +++ b/web/src/components/ak-app-icon.ts @@ -1,5 +1,5 @@ -import { AKElement } from "@goauthentik/app/elements/Base"; -import { PFSize } from "@goauthentik/app/elements/Spinner"; +import { PFSize } from "@goauthentik/common/enums.js"; +import { AKElement } from "@goauthentik/elements/Base"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; diff --git a/web/src/components/ak-event-info.ts b/web/src/components/ak-event-info.ts index 6901f31a81..af1bcc3045 100644 --- a/web/src/components/ak-event-info.ts +++ b/web/src/components/ak-event-info.ts @@ -1,10 +1,10 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { VERSION } from "@goauthentik/common/constants"; +import { PFSize } from "@goauthentik/common/enums.js"; import { EventContext, EventModel, EventWithContext } from "@goauthentik/common/events"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/Expand"; import "@goauthentik/elements/Spinner"; -import { PFSize } from "@goauthentik/elements/Spinner"; import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -16,7 +16,9 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFList from "@patternfly/patternfly/components/List/list.css"; +import PFTable from "@patternfly/patternfly/components/Table/table.css"; import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; +import PFSplit from "@patternfly/patternfly/layouts/Split/split.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { EventActions, FlowsApi } from "@goauthentik/api"; @@ -78,7 +80,9 @@ export class EventInfo extends AKElement { PFButton, PFFlex, PFCard, + PFTable, PFList, + PFSplit, PFDescriptionList, css` code { @@ -243,11 +247,78 @@ export class EventInfo extends AKElement { } renderModelChanged() { + const diff = this.event.context.diff as unknown as { + [key: string]: { + new_value: unknown; + previous_value: unknown; + add?: unknown[]; + remove?: unknown[]; + clear?: boolean; + }; + }; + let diffBody = html``; + if (diff) { + diffBody = html`
      +
      ${msg("Changes made:")}
      + + + + + + + + + + ${Object.keys(diff).map((key) => { + const value = diff[key]; + const previousCol = value.previous_value + ? JSON.stringify(value.previous_value, null, 4) + : msg("-"); + let newCol = html``; + if (value.add || value.remove) { + newCol = html`
        + ${(value.add || value.remove)?.map((item) => { + let itemLabel = ""; + if (value.add) { + itemLabel = msg(str`Added ID ${item}`); + } else if (value.remove) { + itemLabel = msg(str`Removed ID ${item}`); + } + return html`
      • ${itemLabel}
      • `; + })} +
      `; + } else if (value.clear) { + newCol = html`${msg("Cleared")}`; + } else { + newCol = html`
      +${JSON.stringify(value.new_value, null, 4)}
      `; + } + return html` + + + + `; + })} + +
      ${msg("Key")}${msg("Previous value")}${msg("New value")}
      ${key}
      +
      ${previousCol}
      +
      ${newCol}
      +
      + `; + } return html` -
      ${msg("Affected model:")}
      -
      - ${this.getModelInfo(this.event.context?.model as EventModel)} +
      +
      +
      ${msg("Affected model:")}
      +
      + ${this.getModelInfo(this.event.context?.model as EventModel)} +
      +
      + ${diffBody}
      +
      + ${this.renderDefaultResponse()} `; } @@ -285,10 +356,12 @@ export class EventInfo extends AKElement { } renderEmailSent() { + let body = this.event.context.body as string; + body = body.replace("cid:logo.png", "/static/dist/assets/icons/icon_left_brand.png"); return html`
      ${msg("Email info:")}
      ${this.getEmailInfo(this.event.context)}
      - + `; } diff --git a/web/src/components/ak-hint/ak-hint-actions.ts b/web/src/components/ak-hint/ak-hint-actions.ts index 8af341965b..eaf33b2f8e 100644 --- a/web/src/components/ak-hint/ak-hint-actions.ts +++ b/web/src/components/ak-hint/ak-hint-actions.ts @@ -1,4 +1,4 @@ -import { AKElement } from "@goauthentik/app/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; import { css, html } from "lit"; import { customElement } from "lit/decorators.js"; diff --git a/web/src/components/ak-hint/ak-hint-body.ts b/web/src/components/ak-hint/ak-hint-body.ts index f5c122eb50..4261c0a302 100644 --- a/web/src/components/ak-hint/ak-hint-body.ts +++ b/web/src/components/ak-hint/ak-hint-body.ts @@ -1,4 +1,4 @@ -import { AKElement } from "@goauthentik/app/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; import { css, html } from "lit"; import { customElement } from "lit/decorators.js"; diff --git a/web/src/components/ak-hint/ak-hint-footer.ts b/web/src/components/ak-hint/ak-hint-footer.ts index a08197d114..14461a814e 100644 --- a/web/src/components/ak-hint/ak-hint-footer.ts +++ b/web/src/components/ak-hint/ak-hint-footer.ts @@ -1,4 +1,4 @@ -import { AKElement } from "@goauthentik/app/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; import { css, html } from "lit"; import { customElement } from "lit/decorators.js"; diff --git a/web/src/components/ak-hint/ak-hint-title.ts b/web/src/components/ak-hint/ak-hint-title.ts index accc881d0a..abcb9d596c 100644 --- a/web/src/components/ak-hint/ak-hint-title.ts +++ b/web/src/components/ak-hint/ak-hint-title.ts @@ -1,4 +1,4 @@ -import { AKElement } from "@goauthentik/app/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; import { css, html } from "lit"; import { customElement } from "lit/decorators.js"; diff --git a/web/src/components/ak-hint/ak-hint.ts b/web/src/components/ak-hint/ak-hint.ts index 731dbe0ca0..b818fae20d 100644 --- a/web/src/components/ak-hint/ak-hint.ts +++ b/web/src/components/ak-hint/ak-hint.ts @@ -1,4 +1,4 @@ -import { AKElement } from "@goauthentik/app/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; import { css, html } from "lit"; import { customElement } from "lit/decorators.js"; diff --git a/web/src/components/ak-multi-select.ts b/web/src/components/ak-multi-select.ts index 9efddd079e..c7b9601a33 100644 --- a/web/src/components/ak-multi-select.ts +++ b/web/src/components/ak-multi-select.ts @@ -1,5 +1,5 @@ -import "@goauthentik/app/elements/forms/HorizontalFormElement"; import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/forms/HorizontalFormElement"; import { TemplateResult, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; diff --git a/web/src/components/ak-wizard-main/AkWizard.ts b/web/src/components/ak-wizard-main/AkWizard.ts index 48e827c70b..a393020b17 100644 --- a/web/src/components/ak-wizard-main/AkWizard.ts +++ b/web/src/components/ak-wizard-main/AkWizard.ts @@ -1,4 +1,4 @@ -import "@goauthentik/app/components/ak-wizard-main/ak-wizard-frame"; +import "@goauthentik/components/ak-wizard-main/ak-wizard-frame"; import { AKElement } from "@goauthentik/elements/Base"; import { msg } from "@lit/localize"; diff --git a/web/src/components/ak-wizard-main/ak-wizard-frame.ts b/web/src/components/ak-wizard-main/ak-wizard-frame.ts index 01a37af28e..3ec746fed5 100644 --- a/web/src/components/ak-wizard-main/ak-wizard-frame.ts +++ b/web/src/components/ak-wizard-main/ak-wizard-frame.ts @@ -3,7 +3,7 @@ import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; import { msg } from "@lit/localize"; import { customElement, property, query } from "@lit/reactive-element/decorators.js"; -import { TemplateResult, html, nothing } from "lit"; +import { TemplateResult, css, html, nothing } from "lit"; import { classMap } from "lit/directives/class-map.js"; import { map } from "lit/directives/map.js"; @@ -31,7 +31,15 @@ import { type WizardButton, WizardStepLabel } from "./types"; @customElement("ak-wizard-frame") export class AkWizardFrame extends CustomEmitterElement(ModalButton) { static get styles() { - return [...super.styles, PFWizard]; + return [ + ...super.styles, + PFWizard, + css` + .pf-c-modal-box { + height: 75%; + } + `, + ]; } /** @@ -84,7 +92,7 @@ export class AkWizardFrame extends CustomEmitterElement(ModalButton) { ${this.renderHeader()}
      - ${this.renderNavigation()} + ${this.renderNavigation()} ${this.renderMainSection()}
      ${this.renderFooter()} diff --git a/web/src/components/events/ObjectChangelog.ts b/web/src/components/events/ObjectChangelog.ts index 160a98d731..1e2fb80812 100644 --- a/web/src/components/events/ObjectChangelog.ts +++ b/web/src/components/events/ObjectChangelog.ts @@ -1,6 +1,9 @@ +import { EventGeo, EventUser } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; +import { actionToLabel } from "@goauthentik/common/labels"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-event-info"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/Dropdown"; @@ -9,8 +12,8 @@ import "@goauthentik/elements/buttons/SpinnerButton"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { Table, TableColumn } from "@goauthentik/elements/table/Table"; -import { msg, str } from "@lit/localize"; -import { TemplateResult, html } from "lit"; +import { msg } from "@lit/localize"; +import { PropertyValues, TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { Event, EventsApi } from "@goauthentik/api"; @@ -28,27 +31,18 @@ export class ObjectChangelog extends Table { @property() targetModelApp?: string; - private _targetModelName = ""; - @property() - set targetModelName(value: string) { - this._targetModelName = value; - this.fetch(); - } - - get targetModelName(): string { - return this._targetModelName; - } + targetModelName = ""; async apiEndpoint(page: number): Promise> { - let modelName = this._targetModelName; + let modelName = this.targetModelName; let appName = this.targetModelApp; - if (this._targetModelName.indexOf(".") !== -1) { - const parts = this._targetModelName.split("."); + if (this.targetModelName.indexOf(".") !== -1) { + const parts = this.targetModelName.split(".", 1); appName = parts[0]; modelName = parts[1]; } - if (this._targetModelName === "") { + if (this.targetModelName === "") { return Promise.reject(); } return new EventsApi(DEFAULT_CONFIG).eventsEventsList({ @@ -71,17 +65,21 @@ export class ObjectChangelog extends Table { ]; } + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("targetModelName") && this.targetModelName) { + this.fetch(); + } + } + row(item: EventWithContext): TemplateResult[] { return [ - html`${item.action}`, - html`
      ${item.user?.username}
      - ${item.user.on_behalf_of - ? html` - ${msg(str`On behalf of ${item.user.on_behalf_of.username}`)} - ` - : html``}`, - html`${item.created?.toLocaleString()}`, - html`${item.clientIp || msg("-")}`, + html`${actionToLabel(item.action)}`, + EventUser(item), + html`
      ${getRelativeTime(item.created)}
      + ${item.created.toLocaleString()}`, + html`
      ${item.clientIp || msg("-")}
      + + ${EventGeo(item)}`, ]; } diff --git a/web/src/components/events/UserEvents.ts b/web/src/components/events/UserEvents.ts index 8b80657924..55fdb867d7 100644 --- a/web/src/components/events/UserEvents.ts +++ b/web/src/components/events/UserEvents.ts @@ -1,7 +1,9 @@ +import { EventUser } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { actionToLabel } from "@goauthentik/common/labels"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-event-info"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/Dropdown"; @@ -9,7 +11,7 @@ import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; -import { msg, str } from "@lit/localize"; +import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; @@ -46,13 +48,9 @@ export class UserEvents extends Table { row(item: EventWithContext): TemplateResult[] { return [ html`${actionToLabel(item.action)}`, - html`
      ${item.user?.username}
      - ${item.user.on_behalf_of - ? html` - ${msg(str`On behalf of ${item.user.on_behalf_of.username}`)} - ` - : html``}`, - html`${item.created?.toLocaleString()}`, + EventUser(item), + html`
      ${getRelativeTime(item.created)}
      + ${item.created.toLocaleString()}`, html`${item.clientIp || msg("-")}`, ]; } diff --git a/web/src/common/oauth/callback.ts b/web/src/components/oauth/callback.ts similarity index 85% rename from web/src/common/oauth/callback.ts rename to web/src/components/oauth/callback.ts index b52122918b..a7b3228613 100644 --- a/web/src/common/oauth/callback.ts +++ b/web/src/components/oauth/callback.ts @@ -1,5 +1,5 @@ -import { state } from "@goauthentik/app/common/oauth/constants"; -import { refreshMe } from "@goauthentik/app/common/users"; +import { state } from "@goauthentik/components/oauth/constants"; +import { refreshMe } from "@goauthentik/common/users"; import { User, UserManager, UserManagerSettings } from "oidc-client-ts"; import { LitElement } from "lit"; diff --git a/web/src/common/oauth/constants.ts b/web/src/components/oauth/constants.ts similarity index 100% rename from web/src/common/oauth/constants.ts rename to web/src/components/oauth/constants.ts diff --git a/web/src/components/oauth/controller.ts b/web/src/components/oauth/controller.ts new file mode 100644 index 0000000000..f6d8909163 --- /dev/null +++ b/web/src/components/oauth/controller.ts @@ -0,0 +1,41 @@ +import { state } from "@goauthentik/components/oauth/constants"; +import { Interface } from "@goauthentik/elements/Interface/index.js"; +import { UserManager, UserManagerSettings } from "oidc-client-ts"; + +import { ReactiveController, ReactiveControllerHost } from "lit"; + +type ReactiveInterfaceHost = Partial & Interface; + +export class OAuthLoginController implements ReactiveController { + checked = false; + + constructor( + private host: ReactiveInterfaceHost, + private settings: UserManagerSettings + ) { + this.host.addController(this); + } + + hostUpdated() { + if (this.checked) { + return; + } + this.checked = true; + this.ensureLoggedIn(); + } + + private async ensureLoggedIn() { + const client = new UserManager(this.settings); + const user = await client.getUser(); + if (user !== null) { + return; + } + if (window.location.href.startsWith(this.settings.redirect_uri)) { + return; + } + const s = new state(); + await client.signinRedirect({ + state: s, + }); + } +} diff --git a/web/src/common/oauth/settings.ts b/web/src/components/oauth/settings.ts similarity index 93% rename from web/src/common/oauth/settings.ts rename to web/src/components/oauth/settings.ts index 03ed0c6a42..1cbdfee0cd 100644 --- a/web/src/common/oauth/settings.ts +++ b/web/src/components/oauth/settings.ts @@ -1,4 +1,4 @@ -import { MemoryStore } from "@goauthentik/app/common/oauth/storage"; +import { MemoryStore } from "@goauthentik/components/oauth/storage"; import { Log, OidcClientSettings, UserManagerSettings } from "oidc-client-ts"; Log.setLogger(console); diff --git a/web/src/common/oauth/signout.ts b/web/src/components/oauth/signout.ts similarity index 100% rename from web/src/common/oauth/signout.ts rename to web/src/components/oauth/signout.ts diff --git a/web/src/common/oauth/storage.ts b/web/src/components/oauth/storage.ts similarity index 100% rename from web/src/common/oauth/storage.ts rename to web/src/components/oauth/storage.ts diff --git a/web/src/components/stories/ak-search-select.stories.ts b/web/src/components/stories/ak-search-select.stories.ts new file mode 100644 index 0000000000..a2bd77f378 --- /dev/null +++ b/web/src/components/stories/ak-search-select.stories.ts @@ -0,0 +1,145 @@ +import { groupBy } from "@goauthentik/common/utils"; +import { convertToSlug as slugify } from "@goauthentik/common/utils.js"; +import "@goauthentik/elements/forms/SearchSelect/ak-search-select"; +import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect/ak-search-select"; +import { Meta } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +type RawSample = [string, string[]]; +type Sample = { name: string; pk: string; season: string[] }; + +// prettier-ignore +const groupedSamples: RawSample[] = [ + ["Spring", [ + "Apples", "Apricots", "Asparagus", "Avocados", "Bananas", "Broccoli", + "Cabbage", "Carrots", "Celery", "Collard Greens", "Garlic", "Herbs", "Kale", "Kiwifruit", "Lemons", + "Lettuce", "Limes", "Mushrooms", "Onions", "Peas", "Pineapples", "Radishes", "Rhubarb", "Spinach", + "Strawberries", "Swiss Chard", "Turnips"]], + ["Summer", [ + "Apples", "Apricots", "Avocados", "Bananas", "Beets", "Bell Peppers", "Blackberries", "Blueberries", + "Cantaloupe", "Carrots", "Celery", "Cherries", "Corn", "Cucumbers", "Eggplant", "Garlic", + "Green Beans", "Herbs", "Honeydew Melon", "Lemons", "Lima Beans", "Limes", "Mangos", "Okra", "Peaches", + "Plums", "Raspberries", "Strawberries", "Summer Squash", "Tomatillos", "Tomatoes", "Watermelon", + "Zucchini"]], + ["Fall", [ + "Apples", "Bananas", "Beets", "Bell Peppers", "Broccoli", "Brussels Sprouts", "Cabbage", "Carrots", + "Cauliflower", "Celery", "Collard Greens", "Cranberries", "Garlic", "Ginger", "Grapes", "Green Beans", + "Herbs", "Kale", "Kiwifruit", "Lemons", "Lettuce", "Limes", "Mangos", "Mushrooms", "Onions", + "Parsnips", "Pears", "Peas", "Pineapples", "Potatoes", "Pumpkin", "Radishes", "Raspberries", + "Rutabagas", "Spinach", "Sweet Potatoes", "Swiss Chard", "Turnips", "Winter Squash"]], + ["Winter", [ + "Apples", "Avocados", "Bananas", "Beets", "Brussels Sprouts", "Cabbage", "Carrots", "Celery", + "Collard Greens", "Grapefruit", "Herbs", "Kale", "Kiwifruit", "Leeks", "Lemons", "Limes", "Onions", + "Oranges", "Parsnips", "Pears", "Pineapples", "Potatoes", "Pumpkin", "Rutabagas", + "Sweet Potatoes", "Swiss Chard", "Turnips", "Winter Squash"]] +]; + +// WAAAAY too many lines to turn the arrays above into a Sample of +// { name: "Apricots", pk: "apple", season: ["Spring", "Summer"] } +// but it does the job. + +const samples = Array.from( + groupedSamples + .reduce((acc, sample) => { + sample[1].forEach((item) => { + const update = (thing: Sample) => ({ + ...thing, + season: [...thing.season, sample[0]], + }); + acc.set( + item, + update(acc.get(item) || { name: item, pk: slugify(item), season: [] }), + ); + return acc; + }, acc); + return acc; + }, new Map()) + .values(), +); +samples.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + +// All we need is a promise to return our dataset. It doesn't have to be a class-based method a'la +// the authentik API. + +const getSamples = (query = "") => + Promise.resolve( + samples.filter((s) => + query !== "" ? s.name.toLowerCase().includes(query.toLowerCase()) : true, + ), + ); + +const metadata: Meta> = { + title: "Elements / Search Select ", + component: "ak-search-select", + parameters: { + docs: { + description: { + component: "An implementation of the Patternfly search select pattern", + }, + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
      + + + ${testItem} + +
        +
        `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const displayChange = (ev: any) => { + document.getElementById("message-pad")!.innerText = `Value selected: ${JSON.stringify( + ev.detail.value, + null, + 2, + )}`; +}; + +export const Default = () => { + return container( + html` sample.name} + .value=${(sample: Sample) => sample.pk} + @ak-change=${displayChange} + >`, + ); +}; + +export const Grouped = () => { + return container( + html` sample.name} + .value=${(sample: Sample) => sample.pk} + .groupBy=${(samples: Sample[]) => + groupBy(samples, (sample: Sample) => sample.season[0] ?? "")} + @ak-change=${displayChange} + >`, + ); +}; + +export const Selected = () => { + return container( + html` sample.name} + .value=${(sample: Sample) => sample.pk} + .selected=${(sample: Sample) => sample.pk === "herbs"} + @ak-change=${displayChange} + >`, + ); +}; diff --git a/web/src/elements/Alert.ts b/web/src/elements/Alert.ts index e75d2c3936..139c504203 100644 --- a/web/src/elements/Alert.ts +++ b/web/src/elements/Alert.ts @@ -17,6 +17,8 @@ export enum Level { export class Alert extends AKElement { @property({ type: Boolean }) inline = false; + @property({ type: Boolean }) + plain = false; @property() level: Level = Level.Warning; @@ -26,7 +28,11 @@ export class Alert extends AKElement { } render(): TemplateResult { - return html`
        + return html`
        diff --git a/web/src/elements/AuthentikContexts.ts b/web/src/elements/AuthentikContexts.ts index 97a89a8813..df38f6fcf5 100644 --- a/web/src/elements/AuthentikContexts.ts +++ b/web/src/elements/AuthentikContexts.ts @@ -1,7 +1,15 @@ -import { createContext } from "@lit-labs/context"; +import { createContext } from "@lit/context"; -import { type Config } from "@goauthentik/api"; +import type { Config, CurrentBrand, LicenseSummary, SessionUser } from "@goauthentik/api"; export const authentikConfigContext = createContext(Symbol("authentik-config-context")); +export const authentikUserContext = createContext(Symbol("authentik-user-context")); + +export const authentikEnterpriseContext = createContext( + Symbol("authentik-enterprise-context"), +); + +export const authentikBrandContext = createContext(Symbol("authentik-brand-context")); + export default authentikConfigContext; diff --git a/web/src/elements/Base.ts b/web/src/elements/Base.ts index 46c983aad7..5bef84b12d 100644 --- a/web/src/elements/Base.ts +++ b/web/src/elements/Base.ts @@ -1,23 +1,19 @@ -import { config, tenant } from "@goauthentik/common/api/config"; import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; -import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; +import { UIConfig } from "@goauthentik/common/ui/config"; import { adaptCSS } from "@goauthentik/common/utils"; -import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; +import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet"; -import { ContextProvider } from "@lit-labs/context"; import { localized } from "@lit/localize"; -import { CSSResult, LitElement } from "lit"; -import { state } from "lit/decorators.js"; +import { LitElement, ReactiveElement } from "lit"; import AKGlobal from "@goauthentik/common/styles/authentik.css"; import ThemeDark from "@goauthentik/common/styles/theme-dark.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { Config, CurrentTenant, UiThemeEnum } from "@goauthentik/api"; +import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api"; type AkInterface = HTMLElement & { getTheme: () => Promise; - tenant?: CurrentTenant; + brand?: CurrentBrand; uiConfig?: UIConfig; config?: Config; }; @@ -25,13 +21,6 @@ type AkInterface = HTMLElement & { export const rootInterface = (): T | undefined => (document.body.querySelector("[data-ak-interface-root]") as T) ?? undefined; -export function ensureCSSStyleSheet(css: CSSStyleSheet | CSSResult): CSSStyleSheet { - if (css instanceof CSSResult) { - return css.styleSheet!; - } - return css; -} - let css: Promise | undefined; function fetchCustomCSS(): Promise { if (!css) { @@ -52,10 +41,6 @@ function fetchCustomCSS(): Promise { return css; } -export interface AdoptedStyleSheetsElement { - adoptedStyleSheets: readonly CSSStyleSheet[]; -} - const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)"; @localized() @@ -72,18 +57,22 @@ export class AKElement extends LitElement { super(); } - protected createRenderRoot(): ShadowRoot | Element { - const root = super.createRenderRoot() as ShadowRoot; - let styleRoot: AdoptedStyleSheetsElement = root; - if ("ShadyDOM" in window) { - styleRoot = document; - } + setInitialStyles(root: DocumentOrShadowRoot) { + const styleRoot: DocumentOrShadowRoot = ( + "ShadyDOM" in window ? document : root + ) as DocumentOrShadowRoot; styleRoot.adoptedStyleSheets = adaptCSS([ ...styleRoot.adoptedStyleSheets, ensureCSSStyleSheet(AKGlobal), ]); this._initTheme(styleRoot); this._initCustomCSS(styleRoot); + } + + protected createRenderRoot() { + this.fixElementStyles(); + const root = super.createRenderRoot(); + this.setInitialStyles(root as unknown as DocumentOrShadowRoot); return root; } @@ -91,7 +80,14 @@ export class AKElement extends LitElement { return rootInterface()?.getTheme() || UiThemeEnum.Automatic; } - async _initTheme(root: AdoptedStyleSheetsElement): Promise { + fixElementStyles() { + // Ensure all style sheets being passed are really style sheets. + (this.constructor as typeof ReactiveElement).elementStyles = ( + this.constructor as typeof ReactiveElement + ).elementStyles.map(ensureCSSStyleSheet); + } + + async _initTheme(root: DocumentOrShadowRoot): Promise { // Early activate theme based on media query to prevent light flash // when dark is preferred this._activateTheme( @@ -103,7 +99,7 @@ export class AKElement extends LitElement { this._applyTheme(root, await this.getTheme()); } - private async _initCustomCSS(root: AdoptedStyleSheetsElement): Promise { + private async _initCustomCSS(root: DocumentOrShadowRoot): Promise { const sheets = await fetchCustomCSS(); sheets.map((css) => { if (css === "") { @@ -115,7 +111,7 @@ export class AKElement extends LitElement { }); } - _applyTheme(root: AdoptedStyleSheetsElement, theme?: UiThemeEnum): void { + _applyTheme(root: DocumentOrShadowRoot, theme?: UiThemeEnum): void { if (!theme) { theme = UiThemeEnum.Automatic; } @@ -150,7 +146,7 @@ export class AKElement extends LitElement { return undefined; } - _activateTheme(root: AdoptedStyleSheetsElement, theme: UiThemeEnum) { + _activateTheme(root: DocumentOrShadowRoot, theme: UiThemeEnum) { if (theme === this._activeTheme) { return; } @@ -175,49 +171,3 @@ export class AKElement extends LitElement { this.requestUpdate(); } } - -export class Interface extends AKElement implements AkInterface { - @state() - tenant?: CurrentTenant; - - @state() - uiConfig?: UIConfig; - - _configContext = new ContextProvider(this, { - context: authentikConfigContext, - initialValue: undefined, - }); - - _config?: Config; - - @state() - set config(c: Config) { - this._config = c; - this._configContext.setValue(c); - this.requestUpdate(); - } - - get config(): Config | undefined { - return this._config; - } - - constructor() { - super(); - document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)]; - tenant().then((tenant) => (this.tenant = tenant)); - config().then((config) => (this.config = config)); - this.dataset.akInterfaceRoot = "true"; - } - - _activateTheme(root: AdoptedStyleSheetsElement, theme: UiThemeEnum): void { - super._activateTheme(root, theme); - super._activateTheme(document, theme); - } - - async getTheme(): Promise { - if (!this.uiConfig) { - this.uiConfig = await uiConfig(); - } - return this.uiConfig.theme?.base || UiThemeEnum.Automatic; - } -} diff --git a/web/src/elements/EmptyState.ts b/web/src/elements/EmptyState.ts index f419d4e2fe..8842badea2 100644 --- a/web/src/elements/EmptyState.ts +++ b/web/src/elements/EmptyState.ts @@ -1,7 +1,8 @@ +import { PFSize } from "@goauthentik/common/enums.js"; import { AKElement } from "@goauthentik/elements/Base"; -import { PFSize } from "@goauthentik/elements/Spinner"; +import "@goauthentik/elements/Spinner"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; @@ -23,7 +24,17 @@ export class EmptyState extends AKElement { header = ""; static get styles(): CSSResult[] { - return [PFBase, PFEmptyState, PFTitle]; + return [ + PFBase, + PFEmptyState, + PFTitle, + css` + i.pf-c-empty-state__icon { + height: var(--pf-global--icon--FontSize--2xl); + line-height: var(--pf-global--icon--FontSize--2xl); + } + `, + ]; } render(): TemplateResult { diff --git a/web/src/elements/Interface/BrandContextController.ts b/web/src/elements/Interface/BrandContextController.ts new file mode 100644 index 0000000000..ac3106ed58 --- /dev/null +++ b/web/src/elements/Interface/BrandContextController.ts @@ -0,0 +1,51 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts"; +import type { ReactiveElementHost } from "@goauthentik/elements/types.js"; + +import { ContextProvider } from "@lit/context"; +import type { ReactiveController } from "lit"; + +import type { CurrentBrand } from "@goauthentik/api"; +import { CoreApi } from "@goauthentik/api"; + +import type { AkInterface } from "./Interface"; + +export class BrandContextController implements ReactiveController { + host!: ReactiveElementHost; + + context!: ContextProvider<{ __context__: CurrentBrand | undefined }>; + + constructor(host: ReactiveElementHost) { + this.host = host; + this.context = new ContextProvider(this.host, { + context: authentikBrandContext, + initialValue: undefined, + }); + this.fetch = this.fetch.bind(this); + this.fetch(); + } + + fetch() { + new CoreApi(DEFAULT_CONFIG).coreBrandsCurrentRetrieve().then((brand) => { + this.context.setValue(brand); + this.host.brand = brand; + }); + } + + hostConnected() { + window.addEventListener(EVENT_REFRESH, this.fetch); + } + + hostDisconnected() { + window.removeEventListener(EVENT_REFRESH, this.fetch); + } + + hostUpdate() { + // If the Interface changes its brand information for some reason, + // we should notify all users of the context of that change. doesn't + if (this.host.brand !== this.context.value) { + this.context.setValue(this.host.brand); + } + } +} diff --git a/web/src/elements/Interface/ConfigContextController.ts b/web/src/elements/Interface/ConfigContextController.ts new file mode 100644 index 0000000000..c626a7a9c9 --- /dev/null +++ b/web/src/elements/Interface/ConfigContextController.ts @@ -0,0 +1,56 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { globalAK } from "@goauthentik/common/global"; +import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; +import type { ReactiveElementHost } from "@goauthentik/elements/types.js"; + +import { ContextProvider } from "@lit/context"; +import type { ReactiveController } from "lit"; + +import type { Config } from "@goauthentik/api"; +import { RootApi } from "@goauthentik/api"; + +import type { AkInterface } from "./Interface"; + +export class ConfigContextController implements ReactiveController { + host!: ReactiveElementHost; + + context!: ContextProvider<{ __context__: Config | undefined }>; + + constructor(host: ReactiveElementHost) { + this.host = host; + this.context = new ContextProvider(this.host, { + context: authentikConfigContext, + initialValue: undefined, + }); + // Pre-hydrate from template-embedded config + this.context.setValue(globalAK().config); + this.host.config = globalAK().config; + this.fetch = this.fetch.bind(this); + this.fetch(); + } + + fetch() { + new RootApi(DEFAULT_CONFIG).rootConfigRetrieve().then((config) => { + this.context.setValue(config); + this.host.config = config; + }); + } + + hostConnected() { + window.addEventListener(EVENT_REFRESH, this.fetch); + } + + hostDisconnected() { + window.removeEventListener(EVENT_REFRESH, this.fetch); + } + + hostUpdate() { + // If the Interface changes its config information, we should notify all + // users of the context of that change, without creating an infinite + // loop of resets. + if (this.host.config !== this.context.value) { + this.context.setValue(this.host.config); + } + } +} diff --git a/web/src/elements/Interface/EnterpriseContextController.ts b/web/src/elements/Interface/EnterpriseContextController.ts new file mode 100644 index 0000000000..f0cc1ac2a8 --- /dev/null +++ b/web/src/elements/Interface/EnterpriseContextController.ts @@ -0,0 +1,52 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants"; +import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts"; +import type { ReactiveElementHost } from "@goauthentik/elements/types.js"; + +import { ContextProvider } from "@lit/context"; +import type { ReactiveController } from "lit"; + +import type { LicenseSummary } from "@goauthentik/api"; +import { EnterpriseApi } from "@goauthentik/api"; + +import type { AkEnterpriseInterface } from "./Interface"; + +export class EnterpriseContextController implements ReactiveController { + host!: ReactiveElementHost; + + context!: ContextProvider<{ __context__: LicenseSummary | undefined }>; + + constructor(host: ReactiveElementHost) { + this.host = host; + this.context = new ContextProvider(this.host, { + context: authentikEnterpriseContext, + initialValue: undefined, + }); + this.fetch = this.fetch.bind(this); + this.fetch(); + } + + fetch() { + new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => { + this.context.setValue(enterprise); + this.host.licenseSummary = enterprise; + }); + } + + hostConnected() { + window.addEventListener(EVENT_REFRESH_ENTERPRISE, this.fetch); + } + + hostDisconnected() { + window.removeEventListener(EVENT_REFRESH_ENTERPRISE, this.fetch); + } + + hostUpdate() { + // If the Interface changes its config information, we should notify all + // users of the context of that change, without creating an infinite + // loop of resets. + if (this.host.licenseSummary !== this.context.value) { + this.context.setValue(this.host.licenseSummary); + } + } +} diff --git a/web/src/elements/Interface/Interface.ts b/web/src/elements/Interface/Interface.ts new file mode 100644 index 0000000000..8bedbe16e7 --- /dev/null +++ b/web/src/elements/Interface/Interface.ts @@ -0,0 +1,82 @@ +import { UIConfig, uiConfig } from "@goauthentik/common/ui/config"; +import { ModalOrchestrationController } from "@goauthentik/elements/controllers/ModalOrchestrationController.js"; +import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet"; + +import { state } from "lit/decorators.js"; + +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api"; +import { UiThemeEnum } from "@goauthentik/api"; + +import { AKElement } from "../Base"; +import { BrandContextController } from "./BrandContextController"; +import { ConfigContextController } from "./ConfigContextController"; +import { EnterpriseContextController } from "./EnterpriseContextController"; + +export type AkInterface = HTMLElement & { + getTheme: () => Promise; + brand?: CurrentBrand; + uiConfig?: UIConfig; + config?: Config; +}; + +const brandContext = Symbol("brandContext"); +const configContext = Symbol("configContext"); +const modalController = Symbol("modalController"); + +export class Interface extends AKElement implements AkInterface { + @state() + uiConfig?: UIConfig; + + [brandContext]!: BrandContextController; + + [configContext]!: ConfigContextController; + + [modalController]!: ModalOrchestrationController; + + @state() + config?: Config; + + @state() + brand?: CurrentBrand; + + constructor() { + super(); + document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)]; + this[brandContext] = new BrandContextController(this); + this[configContext] = new ConfigContextController(this); + this[modalController] = new ModalOrchestrationController(this); + this.dataset.akInterfaceRoot = "true"; + } + + _activateTheme(root: DocumentOrShadowRoot, theme: UiThemeEnum): void { + super._activateTheme(root, theme); + super._activateTheme(document as unknown as DocumentOrShadowRoot, theme); + } + + async getTheme(): Promise { + if (!this.uiConfig) { + this.uiConfig = await uiConfig(); + } + return this.uiConfig.theme?.base || UiThemeEnum.Automatic; + } +} + +export type AkEnterpriseInterface = AkInterface & { + licenseSummary?: LicenseSummary; +}; + +const enterpriseContext = Symbol("enterpriseContext"); + +export class EnterpriseAwareInterface extends Interface { + [enterpriseContext]!: EnterpriseContextController; + + @state() + licenseSummary?: LicenseSummary; + + constructor() { + super(); + this[enterpriseContext] = new EnterpriseContextController(this); + } +} diff --git a/web/src/elements/Interface/authentikConfigProvider.ts b/web/src/elements/Interface/authentikConfigProvider.ts new file mode 100644 index 0000000000..79f1c960bb --- /dev/null +++ b/web/src/elements/Interface/authentikConfigProvider.ts @@ -0,0 +1,18 @@ +import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; +import type { Constructor } from "@goauthentik/elements/types.js"; + +import { consume } from "@lit/context"; +import type { LitElement } from "lit"; + +import type { Config } from "@goauthentik/api"; + +export function WithAuthentikConfig>( + superclass: T, + subscribe = true, +) { + abstract class WithAkConfigProvider extends superclass { + @consume({ context: authentikConfigContext, subscribe }) + public authentikConfig!: Config; + } + return WithAkConfigProvider; +} diff --git a/web/src/elements/Interface/brandProvider.ts b/web/src/elements/Interface/brandProvider.ts new file mode 100644 index 0000000000..912466d963 --- /dev/null +++ b/web/src/elements/Interface/brandProvider.ts @@ -0,0 +1,18 @@ +import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts"; +import type { AbstractConstructor } from "@goauthentik/elements/types.js"; + +import { consume } from "@lit/context"; +import type { LitElement } from "lit"; + +import type { CurrentBrand } from "@goauthentik/api"; + +export function WithBrandConfig>( + superclass: T, + subscribe = true, +) { + abstract class WithBrandProvider extends superclass { + @consume({ context: authentikBrandContext, subscribe }) + public brand!: CurrentBrand; + } + return WithBrandProvider; +} diff --git a/web/src/elements/Interface/capabilitiesProvider.ts b/web/src/elements/Interface/capabilitiesProvider.ts new file mode 100644 index 0000000000..c8841d880b --- /dev/null +++ b/web/src/elements/Interface/capabilitiesProvider.ts @@ -0,0 +1,67 @@ +import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts"; +import type { AbstractConstructor } from "@goauthentik/elements/types.js"; + +import { consume } from "@lit/context"; +import type { LitElement } from "lit"; + +import { CapabilitiesEnum } from "@goauthentik/api"; +import { Config } from "@goauthentik/api"; + +// Using a unique, lexically scoped, and locally static symbol as the field name for the context +// means that it's inaccessible to any child class looking for it. It's one of the strongest privacy +// guarantees in JavaScript. + +class WCC { + public static readonly capabilitiesConfig: unique symbol = Symbol(); +} + +/** + * withCapabilitiesContext mixes in a single method to any LitElement, `can()`, which takes a + * CapabilitiesEnum and returns true or false. + * + * Usage: + * + * After importing, simply mixin this function: + * + * ``` + * export class AkMyNiftyNewFeature extends withCapabilitiesContext(AKElement) { + * ``` + * + * And then if you need to check on a capability: + * + * ``` + * if (this.can(CapabilitiesEnum.IsEnterprise) { ... } + * ``` + * + * This code re-exports CapabilitiesEnum, so you won't have to import it on a separate line if you + * don't need anything else from the API. + * + * Passing `true` as the second mixin argument will cause the inheriting class to subscribe to the + * configuration context. Should the context be explicitly reset, all active web components that are + * currently active and subscribed to the context will automatically have a `requestUpdate()` + * triggered with the new configuration. + * + */ + +export function WithCapabilitiesConfig>( + superclass: T, + subscribe = true, +) { + abstract class CapabilitiesContext extends superclass { + @consume({ context: authentikConfigContext, subscribe }) + private [WCC.capabilitiesConfig]!: Config; + + can(c: CapabilitiesEnum) { + if (!this[WCC.capabilitiesConfig]) { + throw new Error( + "ConfigContext: Attempted to access site configuration before initialization.", + ); + } + return this[WCC.capabilitiesConfig].capabilities.includes(c); + } + } + + return CapabilitiesContext; +} + +export { CapabilitiesEnum }; diff --git a/web/src/elements/Interface/index.ts b/web/src/elements/Interface/index.ts new file mode 100644 index 0000000000..df771286eb --- /dev/null +++ b/web/src/elements/Interface/index.ts @@ -0,0 +1,4 @@ +import { EnterpriseAwareInterface, Interface } from "./Interface"; + +export { Interface, EnterpriseAwareInterface }; +export default Interface; diff --git a/web/src/elements/Interface/licenseSummaryProvider.ts b/web/src/elements/Interface/licenseSummaryProvider.ts new file mode 100644 index 0000000000..4a73ffab6d --- /dev/null +++ b/web/src/elements/Interface/licenseSummaryProvider.ts @@ -0,0 +1,23 @@ +import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts"; +import { Constructor } from "@goauthentik/elements/types.js"; + +import { consume } from "@lit/context"; +import type { LitElement } from "lit"; + +import type { LicenseSummary } from "@goauthentik/api"; + +export function WithLicenseSummary>( + superclass: T, + subscribe = true, +) { + abstract class WithEnterpriseProvider extends superclass { + @consume({ context: authentikEnterpriseContext, subscribe }) + public licenseSummary!: LicenseSummary; + + get hasEnterpriseLicense() { + return this.licenseSummary?.hasLicense; + } + } + + return WithEnterpriseProvider; +} diff --git a/web/src/elements/LoadingOverlay.ts b/web/src/elements/LoadingOverlay.ts index 25ed896676..8420156df6 100644 --- a/web/src/elements/LoadingOverlay.ts +++ b/web/src/elements/LoadingOverlay.ts @@ -1,5 +1,5 @@ import { AKElement } from "@goauthentik/elements/Base"; -import { PFSize } from "@goauthentik/elements/Spinner"; +import "@goauthentik/elements/EmptyState"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; @@ -33,6 +33,8 @@ export class LoadingOverlay extends AKElement { } render(): TemplateResult { - return html``; + return html` + + `; } } diff --git a/web/src/elements/Markdown.ts b/web/src/elements/Markdown.ts index 4ee60b9c41..896acfa861 100644 --- a/web/src/elements/Markdown.ts +++ b/web/src/elements/Markdown.ts @@ -2,8 +2,10 @@ import { docLink } from "@goauthentik/common/global"; import "@goauthentik/elements/Alert"; import { Level } from "@goauthentik/elements/Alert"; import { AKElement } from "@goauthentik/elements/Base"; +import { matter } from "md-front-matter"; +import * as showdown from "showdown"; -import { CSSResult, TemplateResult, css, html } from "lit"; +import { CSSResult, PropertyValues, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; @@ -11,22 +13,28 @@ import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFList from "@patternfly/patternfly/components/List/list.css"; export interface MarkdownDocument { - html: string; - metadata: { [key: string]: string }; - filename: string; path: string; } export type Replacer = (input: string, md: MarkdownDocument) => string; +const isRelativeLink = /href="(\.[^"]*)"/gm; +const isFile = /[^/]+\.md/; + @customElement("ak-markdown") export class Markdown extends AKElement { - @property({ attribute: false }) - md?: MarkdownDocument; + @property() + md: string = ""; + + @property() + meta: string = ""; @property({ attribute: false }) replacers: Replacer[] = []; + docHtml = ""; + docTitle = ""; + defaultReplacers: Replacer[] = [ this.replaceAdmonitions, this.replaceList, @@ -45,8 +53,10 @@ export class Markdown extends AKElement { ]; } + converter = new showdown.Converter({ metadata: true, tables: true }); + replaceAdmonitions(input: string): string { - const admonitionStart = /:::(\w+)/gm; + const admonitionStart = /:::(\w+)(|\s*$)/gm; const admonitionEnd = /:::/gm; return ( input @@ -62,31 +72,35 @@ export class Markdown extends AKElement { } replaceRelativeLinks(input: string, md: MarkdownDocument): string { - const relativeLink = /href=".(.*)"/gm; - const cwd = process.env.CWD as string; - // cwd will point to $root/web, but the docs are in $root/website/docs - let relPath = md.path.replace(cwd + "site", ""); - if (md.filename === "index.md") { - relPath = relPath.replace("index.md", ""); - } - const baseURL = docLink(""); - const fullURL = `${baseURL}${relPath}.$1`; - return input.replace(relativeLink, `href="${fullURL}" target="_blank"`); + const baseName = md.path.replace(isFile, ""); + const baseUrl = docLink(""); + return input.replace(isRelativeLink, (_match, path) => { + const pathName = path.replace(".md", ""); + const link = `docs/${baseName}${pathName}`; + const url = new URL(link, baseUrl).toString(); + return `href="${url}" _target="blank"`; + }); } - render(): TemplateResult { - if (!this.md) { - return html``; + willUpdate(properties: PropertyValues) { + if (properties.has("md") || properties.has("meta")) { + const parsedContent = matter(this.md); + const parsedHTML = this.converter.makeHtml(parsedContent.content); + const replacers = [...this.defaultReplacers, ...this.replacers]; + this.docTitle = parsedContent?.data?.title ?? ""; + this.docHtml = replacers.reduce( + (html, replacer) => replacer(html, { path: this.meta }), + parsedHTML, + ); } - let finalHTML = this.md.html; - const replacers = [...this.defaultReplacers, ...this.replacers]; - replacers.forEach((r) => { - if (!this.md) { - return; - } - finalHTML = r(finalHTML, this.md); - }); - return html`${this.md?.metadata.title ? html`

        ${this.md.metadata.title}

        ` : html``} - ${unsafeHTML(finalHTML)}`; + } + + render() { + if (!this.md) { + return nothing; + } + + return html`${this.docTitle ? html`

        ${this.docTitle}

        ` : nothing} + ${unsafeHTML(this.docHtml)}`; } } diff --git a/web/src/elements/PageHeader.ts b/web/src/elements/PageHeader.ts index fcdbbeffc5..34ddd511b7 100644 --- a/web/src/elements/PageHeader.ts +++ b/web/src/elements/PageHeader.ts @@ -8,12 +8,13 @@ import { } from "@goauthentik/common/constants"; import { currentInterface } from "@goauthentik/common/sentry"; import { me } from "@goauthentik/common/users"; -import { AKElement, rootInterface } from "@goauthentik/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { customElement, property } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; @@ -23,7 +24,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { EventsApi } from "@goauthentik/api"; @customElement("ak-page-header") -export class PageHeader extends AKElement { +export class PageHeader extends WithBrandConfig(AKElement) { @property() icon?: string; @@ -34,30 +35,11 @@ export class PageHeader extends AKElement { hasNotifications = false; @property() - set header(value: string) { - const tenant = rootInterface()?.tenant; - const currentIf = currentInterface(); - let title = tenant?.brandingTitle || TITLE_DEFAULT; - if (currentIf === "admin") { - title = `${msg("Admin")} - ${title}`; - } - if (value !== "") { - title = `${value} - ${title}`; - } - document.title = title; - this._header = value; - } - - get header(): string { - return this._header; - } + header = ""; @property() description?: string; - @state() - _header = ""; - static get styles(): CSSResult[] { return [ PFBase, @@ -125,6 +107,25 @@ export class PageHeader extends AKElement { }); } + setTitle(header?: string) { + const currentIf = currentInterface(); + let title = this.brand?.brandingTitle || TITLE_DEFAULT; + if (currentIf === "admin") { + title = `${msg("Admin")} - ${title}`; + } + // Prepend the header to the title + if (header !== undefined && header !== "") { + title = `${header} - ${title}`; + } + document.title = title; + } + + willUpdate() { + // Always update title, even if there's no header value set, + // as in that case we still need to return to the generic title + this.setTitle(this.header); + } + renderIcon(): TemplateResult { if (this.icon) { if (this.iconImage && !this.icon.startsWith("fa://")) { diff --git a/web/src/elements/Spinner.ts b/web/src/elements/Spinner.ts index 7483aff276..afdbbd527a 100644 --- a/web/src/elements/Spinner.ts +++ b/web/src/elements/Spinner.ts @@ -1,3 +1,4 @@ +import { PFSize } from "@goauthentik/common/enums.js"; import { AKElement } from "@goauthentik/elements/Base"; import { msg } from "@lit/localize"; @@ -6,13 +7,6 @@ import { customElement, property } from "lit/decorators.js"; import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css"; -export enum PFSize { - Small = "pf-m-sm", - Medium = "pf-m-md", - Large = "pf-m-lg", - XLarge = "pf-m-xl", -} - @customElement("ak-spinner") export class Spinner extends AKElement { @property() diff --git a/web/src/elements/SyncStatusCard.ts b/web/src/elements/SyncStatusCard.ts new file mode 100644 index 0000000000..9009322027 --- /dev/null +++ b/web/src/elements/SyncStatusCard.ts @@ -0,0 +1,119 @@ +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { getRelativeTime } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-status-label"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/EmptyState"; +import "@goauthentik/elements/events/LogViewer"; + +import { msg, str } from "@lit/localize"; +import { CSSResult, TemplateResult, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { SyncStatus, SystemTask, SystemTaskStatusEnum } from "@goauthentik/api"; + +@customElement("ak-sync-status-card") +export class SyncStatusCard extends AKElement { + @state() + syncState?: SyncStatus; + + @state() + loading = false; + + @property({ attribute: false }) + fetch!: () => Promise; + + @property({ attribute: false }) + triggerSync!: () => Promise; + + static get styles(): CSSResult[] { + return [PFBase, PFCard]; + } + + firstUpdated() { + this.loading = true; + this.fetch().then((status) => { + this.syncState = status; + this.loading = false; + }); + } + + renderSyncTask(task: SystemTask): TemplateResult { + return html`
      • + ${(this.syncState?.tasks || []).length > 1 ? html`${task.name}` : nothing} + + ${msg( + str`Finished ${getRelativeTime(task.finishTimestamp)} (${task.finishTimestamp.toLocaleString()})`, + )} + +
      • `; + } + + renderSyncStatus(): TemplateResult { + if (this.loading) { + return html``; + } + if (!this.syncState) { + return html`${msg("No sync status.")}`; + } + if (this.syncState.isRunning) { + return html`${msg("Sync currently running.")}`; + } + if (this.syncState.tasks.length < 1) { + return html`${msg("Not synced yet.")}`; + } + return html` +
          + ${this.syncState.tasks.map((task) => { + return this.renderSyncTask(task); + })} +
        + `; + } + + render(): TemplateResult { + return html`
        +
        ${msg("Sync status")}
        +
        ${this.renderSyncStatus()}
        + +
        `; + } +} diff --git a/web/src/elements/TreeView.ts b/web/src/elements/TreeView.ts index 115a1045ad..408a1bd131 100644 --- a/web/src/elements/TreeView.ts +++ b/web/src/elements/TreeView.ts @@ -50,7 +50,7 @@ export class TreeViewNode extends AKElement { return pathItems.reverse().join(this.separator); } - protected createRenderRoot(): Element { + protected createRenderRoot() { return this; } @@ -171,8 +171,7 @@ export class TreeView extends AKElement { } return item; } else { - const child = this.createNode(path, parentItem.childItems[idx], level + 1); - return child; + return this.createNode(path, parentItem.childItems[idx], level + 1); } } diff --git a/web/src/elements/ak-checkbox-group/ak-checkbox-group.stories.ts b/web/src/elements/ak-checkbox-group/ak-checkbox-group.stories.ts new file mode 100644 index 0000000000..11ec69e094 --- /dev/null +++ b/web/src/elements/ak-checkbox-group/ak-checkbox-group.stories.ts @@ -0,0 +1,112 @@ +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +import "./ak-checkbox-group"; +import { CheckboxGroup as AkCheckboxGroup } from "./ak-checkbox-group"; + +const metadata: Meta = { + title: "Elements / Checkbox Group", + component: "ak-checkbox-group", + parameters: { + docs: { + description: { + component: "A stylized value control for check buttons", + }, + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
        + + + ${testItem} + +
          +
          `; + +const testOptions = [ + { label: "Option One: funky", name: "funky" }, + { label: "Option Two: invalid", name: "invalid" }, + { label: "Option Three: weird", name: "weird" }, +]; + +export const CheckboxGroup = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const displayChange = (ev: any) => { + document.getElementById("check-message-pad")!.innerHTML = ` +

          Values selected on target: ${ev.target.value.join(", ")}

          +

          Values sent in event: ${ev.detail.join(", ")}

          +

          Values present as data-ak-control: ${JSON.stringify(ev.target.json, null)}

          `; + }; + + return container( + html`

          + Evented example. Intercept the input event and display the value seen in + the event target. +

          + + `, + ); +}; + +type FDType = [string, string | FormDataEntryValue]; + +export const FormCheckboxGroup = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const displayChange = (ev: any) => { + ev.preventDefault(); + const formData = new FormData(ev.target); + + const valList = Array.from(formData) + .map(([_key, val]: FDType) => val) + .join(", "); + + const fdList = Array.from(formData) + .map( + ([key, val]: FDType) => + `${encodeURIComponent(key)}=${encodeURIComponent(val as string)}`, + ) + .join("&"); + + document.getElementById("check-message-pad")!.innerHTML = ` +

          Values as seen in \`form.formData\`: ${valList}

          +

          Values as seen in x-form-encoded format: ${fdList}

          `; + }; + + return container( + html`

          + FormData example. This variant emits the same events and exhibits the same behavior + as the above, but instead of monitoring for 'change' events on the checkbox group, + we monitor for the user pressing the 'submit' button. What is displayed is the + values as understood by the <form> object, via its internal \`formData\` + field, to demonstrate that this component works with forms as if it were a native + form element. +

          + +
          + + +
          `, + ); +}; diff --git a/web/src/elements/ak-checkbox-group/ak-checkbox-group.ts b/web/src/elements/ak-checkbox-group/ak-checkbox-group.ts new file mode 100644 index 0000000000..addb13395a --- /dev/null +++ b/web/src/elements/ak-checkbox-group/ak-checkbox-group.ts @@ -0,0 +1,227 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { msg } from "@lit/localize"; +import { PropertyValues } from "@lit/reactive-element/reactive-element"; +import { TemplateResult, css, html } from "lit"; +import { customElement, property, queryAll, state } from "lit/decorators.js"; +import { map } from "lit/directives/map.js"; + +import PFCheck from "@patternfly/patternfly/components/Check/check.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +type CheckboxKv = { name: string; label: string | TemplateResult }; +type CheckboxPr = [string, string | TemplateResult]; +export type CheckboxPair = CheckboxKv | CheckboxPr; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const isCheckboxPr = (t: any): t is CheckboxPr => Array.isArray(t); +function* kvToPairs(items: CheckboxPair[]): Iterable { + for (const item of items) { + yield isCheckboxPr(item) ? item : [item.name, item.label]; + } +} + +const AkElementWithCustomEvents = CustomEmitterElement(AKElement); + +/** + * @element ak-checkbox-group + * + * @class CheckboxGroup + * + * @description + * CheckboxGroup renders a collection of checkboxes in a linear list. Multiple + * checkboxes may be picked. + * + * @attr {options} - An array of either `[string, string | TemplateResult]` or + * `{ name: string, label: string | TemplateResult }`. The first value or + * `name` field must be a valid HTML identifier compatible with the HTML + * `name` attribute. + * + * @attr {value} - An array of `name` values corresponding to the options that + * are selected when the element is rendered. + * + * @attr {name} - The name of this element as it will appear in any
          + * transaction + * + * @attr {required} - If true, and if name is set, and no values are chosen, + * will automatically fail a form `submit` event, providing a warning + * message for any labeling. Note: if `name` is not set, this has no effect, + * and a warn() will appear on the console. + * + * @event {input} - Fired when the component's value has changed. Current value + * as an array of `name` will be in the `Event.detail` field. + * + * @event {change} - Fired when the component's value has changed. Current value + * as an array of `name` will be in the `Event.detail` field. + * + * @csspart checkbox - The div containing the checkbox item and the label + * @csspart label - the label + * @csspart input - the input item + * @csspart checkbox-group - the wrapper div with flexbox control + * + * ## Bigger hit area + * + * Providing properly formatted names for selections allows the element to + * associate the label with the event, so the entire horizontal area from + * checkbox to end-of-label will be the hit area. + * + * ## FormAssociated compliance + * + * If a component is a parent, this component will correctly send its + * values to the form for `x-form-encoded` data; multiples will appear in the + * form of `name=value1&name=value2` format, and must be unpacked into an array + * correctly on the server side according to the CGI (common gateway interface) + * protocol. + * + */ + +@customElement("ak-checkbox-group") +export class CheckboxGroup extends AkElementWithCustomEvents { + static get styles() { + return [ + PFBase, + PFForm, + PFCheck, + css` + .pf-c-form__group-control { + padding-top: calc( + var(--pf-c-form--m-horizontal__group-label--md--PaddingTop) * 1.3 + ); + } + `, + ]; + } + + static get formAssociated() { + return true; + } + + @property({ type: Array }) + options: CheckboxPair[] = []; + + @property({ type: Array }) + value: string[] = []; + + @property({ type: String }) + name?: string; + + @property({ type: Boolean }) + required = false; + + @queryAll('input[type="checkbox"]') + checkboxes!: NodeListOf; + + @state() + values: string[] = []; + + internals?: ElementInternals; + doneFirstUpdate = false; + + json() { + return this.values; + } + + private get formValue() { + if (this.name === undefined) { + throw new Error("This cannot be called without having the name set."); + } + const name = this.name; + const entries = new FormData(); + this.values.forEach((v) => entries.append(name, v)); + return entries; + } + + constructor() { + super(); + this.onClick = this.onClick.bind(this); + this.dataset.akControl = "true"; + } + + onClick(ev: Event) { + ev.stopPropagation(); + this.values = Array.from(this.checkboxes) + .filter((checkbox) => checkbox.checked) + .map((checkbox) => checkbox.name); + this.dispatchCustomEvent("change", this.values); + this.dispatchCustomEvent("input", this.values); + if (this.internals) { + this.internals.setValidity({}); + if (this.required && this.values.length === 0) { + this.internals.setValidity( + { + valueMissing: true, + }, + msg("A selection is required"), + this, + ); + } + this.internals.setFormValue(this.formValue); + } + // Doing a write-back so anyone examining the checkbox.value field will get something + // meaningful. Doesn't do anything for anyone, usually, but it's nice to have. + this.value = this.values; + } + + willUpdate(changed: PropertyValues) { + if (changed.has("value") && !this.doneFirstUpdate) { + this.doneFirstUpdate = true; + this.values = this.value; + } + } + + connectedCallback() { + super.connectedCallback(); + if (this.name && !this.internals) { + this.internals = this.attachInternals(); + } + if (this.internals && this.name) { + this.internals.ariaRequired = this.required ? "true" : "false"; + } + if (this.required && !this.internals) { + console.warn( + "Setting `required` on ak-checkbox-group has no effect when the `name` attribute is unset", + ); + } + // These are necessary to prevent the input components' own events from + // leaking out. This helps maintain the illusion that this component + // behaves similarly to the multiple selection behavior of, well, + // + +
          `; + }; + + return html`
          + ${map(kvToPairs(this.options), renderOne)} +
          `; + } +} diff --git a/web/src/elements/ak-dual-select/ak-dual-select-provider.ts b/web/src/elements/ak-dual-select/ak-dual-select-provider.ts new file mode 100644 index 0000000000..15f274460a --- /dev/null +++ b/web/src/elements/ak-dual-select/ak-dual-select-provider.ts @@ -0,0 +1,150 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { debounce } from "@goauthentik/elements/utils/debounce"; +import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { msg } from "@lit/localize"; +import { PropertyValues, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { createRef, ref } from "lit/directives/ref.js"; +import type { Ref } from "lit/directives/ref.js"; + +import type { Pagination } from "@goauthentik/api"; + +import "./ak-dual-select"; +import { AkDualSelect } from "./ak-dual-select"; +import type { DataProvider, DualSelectPair } from "./types"; + +/** + * @element ak-dual-select-provider + * + * A top-level component that understands how the authentik pagination interface works, + * and can provide new pages based upon navigation requests. This is the interface + * between authentik and the generic ak-dual-select component; aside from knowing that + * the Pagination object "looks like Django," the interior components don't know anything + * about authentik at all and could be dropped into Gravity unchanged.) + * + */ + +@customElement("ak-dual-select-provider") +export class AkDualSelectProvider extends CustomListenerElement(AKElement) { + /** A function that takes a page and returns the DualSelectPair[] collection with which to update + * the "Available" pane. + */ + @property({ type: Object }) + provider!: DataProvider; + + @property({ type: Array }) + selected: DualSelectPair[] = []; + + @property({ attribute: "available-label" }) + availableLabel = msg("Available options"); + + @property({ attribute: "selected-label" }) + selectedLabel = msg("Selected options"); + + /** The remote lists are debounced by definition. This is the interval for the debounce. */ + @property({ attribute: "search-delay", type: Number }) + searchDelay = 250; + + @state() + private options: DualSelectPair[] = []; + + private dualSelector: Ref = createRef(); + + private isLoading = false; + + private doneFirstUpdate = false; + private internalSelected: DualSelectPair[] = []; + + private pagination?: Pagination; + + constructor() { + super(); + setTimeout(() => this.fetch(1), 0); + // Notify AkForElementHorizontal how to handle this thing. + this.dataset.akControl = "true"; + this.onNav = this.onNav.bind(this); + this.onChange = this.onChange.bind(this); + this.onSearch = this.onSearch.bind(this); + this.addCustomListener("ak-pagination-nav-to", this.onNav); + this.addCustomListener("ak-dual-select-change", this.onChange); + this.addCustomListener("ak-dual-select-search", this.onSearch); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("selected") && !this.doneFirstUpdate) { + this.doneFirstUpdate = true; + this.internalSelected = this.selected; + } + + if (changedProperties.has("searchDelay")) { + this.doSearch = debounce( + AkDualSelectProvider.prototype.doSearch.bind(this), + this.searchDelay, + ); + } + + if (changedProperties.has("provider")) { + this.pagination = undefined; + this.fetch(); + } + } + + async fetch(page?: number, search = "") { + if (this.isLoading) { + return; + } + this.isLoading = true; + const goto = page ?? this.pagination?.current ?? 1; + const data = await this.provider(goto, search); + this.pagination = data.pagination; + this.options = data.options; + this.isLoading = false; + } + + onNav(event: Event) { + if (!(event instanceof CustomEvent)) { + throw new Error(`Expecting a CustomEvent for navigation, received ${event} instead`); + } + this.fetch(event.detail); + } + + onChange(event: Event) { + if (!(event instanceof CustomEvent)) { + throw new Error(`Expecting a CustomEvent for change, received ${event} instead`); + } + this.internalSelected = event.detail.value; + this.selected = this.internalSelected; + } + + onSearch(event: Event) { + if (!(event instanceof CustomEvent)) { + throw new Error(`Expecting a CustomEvent for change, received ${event} instead`); + } + this.doSearch(event.detail); + } + + doSearch(search: string) { + this.pagination = undefined; + this.fetch(undefined, search); + } + + get value() { + return this.dualSelector.value!.selected.map(([k, _]) => k); + } + + json() { + return this.value; + } + + render() { + return html``; + } +} diff --git a/web/src/elements/ak-dual-select/ak-dual-select.ts b/web/src/elements/ak-dual-select/ak-dual-select.ts new file mode 100644 index 0000000000..41799d56de --- /dev/null +++ b/web/src/elements/ak-dual-select/ak-dual-select.ts @@ -0,0 +1,353 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { + CustomEmitterElement, + CustomListenerElement, +} from "@goauthentik/elements/utils/eventEmitter"; + +import { msg, str } from "@lit/localize"; +import { PropertyValues, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { createRef, ref } from "lit/directives/ref.js"; +import type { Ref } from "lit/directives/ref.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; + +import { globalVariables, mainStyles } from "./components/styles.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import "./components/ak-dual-select-available-pane"; +import { AkDualSelectAvailablePane } from "./components/ak-dual-select-available-pane"; +import "./components/ak-dual-select-controls"; +import "./components/ak-dual-select-selected-pane"; +import { AkDualSelectSelectedPane } from "./components/ak-dual-select-selected-pane"; +import "./components/ak-pagination"; +import "./components/ak-search-bar"; +import { + EVENT_ADD_ALL, + EVENT_ADD_ONE, + EVENT_ADD_SELECTED, + EVENT_DELETE_ALL, + EVENT_REMOVE_ALL, + EVENT_REMOVE_ONE, + EVENT_REMOVE_SELECTED, +} from "./constants"; +import type { BasePagination, DualSelectPair, SearchbarEvent } from "./types"; + +function alphaSort([_k1, v1, s1]: DualSelectPair, [_k2, v2, s2]: DualSelectPair) { + const [l, r] = [s1 !== undefined ? s1 : v1, s2 !== undefined ? s2 : v2]; + return l < r ? -1 : l > r ? 1 : 0; +} + +function mapDualPairs(pairs: DualSelectPair[]) { + return new Map(pairs.map(([k, v, _]) => [k, v])); +} + +const styles = [PFBase, PFButton, globalVariables, mainStyles]; + +/** + * @element ak-dual-select + * + * A master (but independent) component that shows two lists-- one of "available options" and one of + * "selected options". The Available Options panel supports pagination if it receives a valid and + * active pagination object (based on Django's pagination object) from the invoking component. + * + * @fires ak-dual-select-change - A custom change event with the current `selected` list. + */ + +const keyfinder = + (key: string) => + ([k]: DualSelectPair) => + k === key; + +@customElement("ak-dual-select") +export class AkDualSelect extends CustomEmitterElement(CustomListenerElement(AKElement)) { + static get styles() { + return styles; + } + + /* The list of options to *currently* show. Note that this is not *all* the options, only the + * currently shown list of options from a pagination collection. */ + @property({ type: Array }) + options: DualSelectPair[] = []; + + /* The list of options selected. This is the *entire* list and will not be paginated. */ + @property({ type: Array }) + selected: DualSelectPair[] = []; + + @property({ type: Object }) + pages?: BasePagination; + + @property({ attribute: "available-label" }) + availableLabel = msg("Available options"); + + @property({ attribute: "selected-label" }) + selectedLabel = msg("Selected options"); + + @state() + selectedFilter: string = ""; + + availablePane: Ref = createRef(); + + selectedPane: Ref = createRef(); + + selectedKeys: Set = new Set(); + + constructor() { + super(); + this.handleMove = this.handleMove.bind(this); + this.handleSearch = this.handleSearch.bind(this); + [ + EVENT_ADD_ALL, + EVENT_ADD_SELECTED, + EVENT_DELETE_ALL, + EVENT_REMOVE_ALL, + EVENT_REMOVE_SELECTED, + EVENT_ADD_ONE, + EVENT_REMOVE_ONE, + ].forEach((eventName: string) => { + this.addCustomListener(eventName, (event: Event) => this.handleMove(eventName, event)); + }); + this.addCustomListener("ak-dual-select-move", () => { + this.requestUpdate(); + }); + this.addCustomListener("ak-search", this.handleSearch); + } + + willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has("selected")) { + this.selectedKeys = new Set(this.selected.map(([key, _]) => key)); + } + // Pagination invalidates available moveables. + if (changedProperties.has("options") && this.availablePane.value) { + this.availablePane.value.clearMove(); + } + } + + handleMove(eventName: string, event: Event) { + if (!(event instanceof CustomEvent)) { + throw new Error(`Expected move event here, got ${eventName}`); + } + + switch (eventName) { + case EVENT_ADD_SELECTED: { + this.addSelected(); + break; + } + case EVENT_REMOVE_SELECTED: { + this.removeSelected(); + break; + } + case EVENT_ADD_ALL: { + this.addAllVisible(); + break; + } + case EVENT_REMOVE_ALL: { + this.removeAllVisible(); + break; + } + case EVENT_DELETE_ALL: { + this.removeAll(); + break; + } + case EVENT_ADD_ONE: { + this.addOne(event.detail); + break; + } + case EVENT_REMOVE_ONE: { + this.removeOne(event.detail); + break; + } + + default: + throw new Error( + `AkDualSelect.handleMove received unknown event type: ${eventName}`, + ); + } + this.dispatchCustomEvent("ak-dual-select-change", { value: this.value }); + event.stopPropagation(); + } + + addSelected() { + if (this.availablePane.value!.moveable.length === 0) { + return; + } + this.selected = this.availablePane.value!.moveable.reduce( + (acc, key) => { + const value = this.options.find(keyfinder(key)); + return value && !acc.find(keyfinder(value[0])) ? [...acc, value] : acc; + }, + [...this.selected], + ); + // This is where the information gets... lossy. Dammit. + this.availablePane.value!.clearMove(); + } + + addOne(key: string) { + const requested = this.options.find(keyfinder(key)); + if (requested && !this.selected.find(keyfinder(requested[0]))) { + this.selected = [...this.selected, requested]; + } + } + + // These are the *currently visible* options; the parent node is responsible for paginating and + // updating the list of currently visible options; + addAllVisible() { + // Create a new array of all current options and selected, and de-dupe. + const selected = mapDualPairs([...this.options, ...this.selected]); + this.selected = Array.from(selected.entries()); + this.availablePane.value!.clearMove(); + } + + removeSelected() { + if (this.selectedPane.value!.moveable.length === 0) { + return; + } + const deselected = new Set(this.selectedPane.value!.moveable); + this.selected = this.selected.filter(([key]) => !deselected.has(key)); + this.selectedPane.value!.clearMove(); + } + + removeOne(key: string) { + this.selected = this.selected.filter(([k]) => k !== key); + } + + removeAllVisible() { + // Remove all the items from selected that are in the *currently visible* options list + const options = new Set(this.options.map(([k, _]) => k)); + this.selected = this.selected.filter(([k]) => !options.has(k)); + this.selectedPane.value!.clearMove(); + } + + removeAll() { + this.selected = []; + this.selectedPane.value!.clearMove(); + } + + handleSearch(event: SearchbarEvent) { + switch (event.detail.source) { + case "ak-dual-list-available-search": + return this.handleAvailableSearch(event.detail.value); + case "ak-dual-list-selected-search": + return this.handleSelectedSearch(event.detail.value); + } + event.stopPropagation(); + } + + handleAvailableSearch(value: string) { + this.dispatchCustomEvent("ak-dual-select-search", value); + } + + handleSelectedSearch(value: string) { + this.selectedFilter = value; + this.selectedPane.value!.clearMove(); + } + + get value() { + return this.selected; + } + + get canAddAll() { + // False unless any visible option cannot be found in the selected list, so can still be + // added. + const allMoved = + this.options.length === + this.options.filter(([key, _]) => this.selectedKeys.has(key)).length; + + return this.options.length > 0 && !allMoved; + } + + get canRemoveAll() { + // False if no visible option can be found in the selected list + return ( + this.options.length > 0 && !!this.options.find(([key, _]) => this.selectedKeys.has(key)) + ); + } + + get needPagination() { + return (this.pages?.next ?? 0) > 0 || (this.pages?.previous ?? 0) > 0; + } + + render() { + const selected = + this.selectedFilter === "" + ? this.selected + : this.selected.filter(([_k, v, s]) => { + const value = s !== undefined ? s : v; + if (typeof value !== "string") { + throw new Error("Filter only works when there's a string comparator"); + } + return value.toLowerCase().includes(this.selectedFilter.toLowerCase()); + }); + + const availableCount = this.availablePane.value?.toMove.size ?? 0; + const selectedCount = this.selectedPane.value?.toMove.size ?? 0; + const selectedTotal = selected.length; + const availableStatus = + availableCount > 0 ? msg(str`${availableCount} item(s) marked to add.`) : " "; + const selectedTotalStatus = msg(str`${selectedTotal} item(s) selected.`); + const selectedCountStatus = + selectedCount > 0 ? " " + msg(str`${selectedCount} item(s) marked to remove.`) : ""; + const selectedStatus = `${selectedTotalStatus} ${selectedCountStatus}`; + + return html` +
          +
          +
          +
          +
          + ${this.availableLabel} +
          +
          +
          + +
          + ${unsafeHTML(availableStatus)} +
          + + ${this.needPagination + ? html`` + : nothing} +
          + 0} + ?remove-active=${(this.selectedPane.value?.moveable.length ?? 0) > 0} + ?add-all-active=${this.canAddAll} + ?remove-all-active=${this.canRemoveAll} + ?delete-all-active=${this.selected.length !== 0} + enable-select-all + enable-delete-all + > +
          +
          +
          +
          + ${this.selectedLabel} +
          +
          +
          + +
          + ${unsafeHTML(selectedStatus)} +
          + + +
          +
          + `; + } +} diff --git a/web/src/elements/ak-dual-select/components/ak-dual-select-available-pane.ts b/web/src/elements/ak-dual-select/components/ak-dual-select-available-pane.ts new file mode 100644 index 0000000000..b8f58aab9b --- /dev/null +++ b/web/src/elements/ak-dual-select/components/ak-dual-select-available-pane.ts @@ -0,0 +1,159 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import { map } from "lit/directives/map.js"; + +import { availablePaneStyles, listStyles } from "./styles.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFDualListSelector from "@patternfly/patternfly/components/DualListSelector/dual-list-selector.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { EVENT_ADD_ONE } from "../constants"; +import type { DualSelectPair } from "../types"; + +const styles = [PFBase, PFButton, PFDualListSelector, listStyles, availablePaneStyles]; + +const hostAttributes = [ + ["aria-labelledby", "dual-list-selector-available-pane-status"], + ["aria-multiselectable", "true"], + ["role", "listbox"], +]; + +/** + * @element ak-dual-select-available-panel + * + * The "available options" or "left" pane in a dual-list multi-select. It receives from its parent a + * list of options to show *now*, the list of all "selected" options, and maintains an internal list + * of objects selected to move. "selected" options are marked with a checkmark to show they're + * already in the "selected" collection and would be pointless to move. + * + * @fires ak-dual-select-available-move-changed - When the list of "to move" entries changed. + * Includes the current * `toMove` content. + * + * @fires ak-dual-select-add-one - Double-click with the element clicked on. + * + * It is not expected that the `ak-dual-select-available-move-changed` event will be used; instead, + * the attribute will be read by the parent when a control is clicked. + * + */ +@customElement("ak-dual-select-available-pane") +export class AkDualSelectAvailablePane extends CustomEmitterElement(AKElement) { + static get styles() { + return styles; + } + + /* The array of key/value pairs this pane is currently showing */ + @property({ type: Array }) + readonly options: DualSelectPair[] = []; + + /* A set (set being easy for lookups) of keys with all the pairs selected, so that the ones + * currently being shown that have already been selected can be marked and their clicks ignored. + * + */ + @property({ type: Object }) + readonly selected: Set = new Set(); + + /* This is the only mutator for this object. It collects the list of objects the user has + * clicked on *in this pane*. It is explicitly marked as "public" to emphasize that the parent + * orchestrator for the dual-select widget can and will access it to get the list of keys to be + * moved (removed) if the user so requests. + * + */ + @state() + public toMove: Set = new Set(); + + constructor() { + super(); + this.onClick = this.onClick.bind(this); + this.onMove = this.onMove.bind(this); + } + + connectedCallback() { + super.connectedCallback(); + hostAttributes.forEach(([attr, value]) => { + if (!this.hasAttribute(attr)) { + this.setAttribute(attr, value); + } + }); + } + + clearMove() { + this.toMove = new Set(); + } + + onClick(key: string) { + if (this.selected.has(key)) { + return; + } + if (this.toMove.has(key)) { + this.toMove.delete(key); + } else { + this.toMove.add(key); + } + this.dispatchCustomEvent( + "ak-dual-select-available-move-changed", + Array.from(this.toMove.values()).sort(), + ); + this.dispatchCustomEvent("ak-dual-select-move"); + // Necessary because updating a map won't trigger a state change + this.requestUpdate(); + } + + onMove(key: string) { + this.toMove.delete(key); + this.dispatchCustomEvent(EVENT_ADD_ONE, key); + this.requestUpdate(); + } + + get moveable() { + return Array.from(this.toMove.values()); + } + + // DO NOT use `Array.map()` instead of Lit's `map()` function. Lit's `map()` is object-aware and + // will not re-arrange or reconstruct the list automatically if the actual sources do not + // change; this allows the available pane to illustrate selected items with the checkmark + // without causing the list to scroll back up to the top. + + render() { + return html` +
          +
            + ${map(this.options, ([key, label]) => { + const selected = classMap({ + "pf-m-selected": this.toMove.has(key), + }); + return html`
          • this.onClick(key)} + @dblclick=${() => this.onMove(key)} + role="option" + data-ak-key=${key} + tabindex="-1" + > +
            + + + ${label}${this.selected.has(key) + ? html`` + : nothing} +
            +
          • `; + })} +
          +
          + `; + } +} + +export default AkDualSelectAvailablePane; diff --git a/web/src/elements/ak-dual-select/components/ak-dual-select-controls.ts b/web/src/elements/ak-dual-select/components/ak-dual-select-controls.ts new file mode 100644 index 0000000000..32d3761760 --- /dev/null +++ b/web/src/elements/ak-dual-select/components/ak-dual-select-controls.ts @@ -0,0 +1,165 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { msg } from "@lit/localize"; +import { css, html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { + EVENT_ADD_ALL, + EVENT_ADD_SELECTED, + EVENT_DELETE_ALL, + EVENT_REMOVE_ALL, + EVENT_REMOVE_SELECTED, +} from "../constants"; + +const styles = [ + PFBase, + PFButton, + css` + :host { + align-self: center; + padding-right: var(--pf-c-dual-list-selector__controls--PaddingRight); + padding-left: var(--pf-c-dual-list-selector__controls--PaddingLeft); + } + .pf-c-dual-list-selector { + max-width: 4rem; + } + .ak-dual-list-selector__controls { + display: grid; + justify-content: center; + align-content: center; + height: 100%; + } + `, +]; + +/** + * @element ak-dual-select-controls + * + * The "control box" for a dual-list multi-select. It's controlled by the parent orchestrator as to + * whether or not any of its controls are enabled. It sends a variety of messages to the parent + * orchestrator which will then reconcile the "available" and "selected" panes at need. + * + */ + +@customElement("ak-dual-select-controls") +export class AkDualSelectControls extends CustomEmitterElement(AKElement) { + static get styles() { + return styles; + } + + /* Set to true if any *visible* elements can be added to the selected list + */ + @property({ attribute: "add-active", type: Boolean }) + addActive = false; + + /* Set to true if any elements can be removed from the selected list (essentially, + * if the selected list is not empty) + */ + @property({ attribute: "remove-active", type: Boolean }) + removeActive = false; + + /* Set to true if *all* the currently visible elements can be moved + * into the selected list (essentially, if any visible elements are + * not currently selected) + */ + @property({ attribute: "add-all-active", type: Boolean }) + addAllActive = false; + + /* Set to true if *any* of the elements currently visible in the available + * pane are available to be moved to the selected list, enabling that + * all of those specific elements be moved out of the selected list + */ + @property({ attribute: "remove-all-active", type: Boolean }) + removeAllActive = false; + + /* if deleteAll is enabled, set to true to show that there are elements in the + * selected list that can be deleted. + */ + @property({ attribute: "delete-all-active", type: Boolean }) + enableDeleteAll = false; + + /* Set to true if you want the `...AllActive` buttons made available. */ + @property({ attribute: "enable-select-all", type: Boolean }) + selectAll = false; + + /* Set to true if you want the `ClearAllSelected` button made available */ + @property({ attribute: "enable-delete-all", type: Boolean }) + deleteAll = false; + + constructor() { + super(); + this.onClick = this.onClick.bind(this); + } + + onClick(eventName: string) { + this.dispatchCustomEvent(eventName); + } + + renderButton(label: string, event: string, active: boolean, direction: string) { + return html` +
          + +
          +
          `; + } + + render() { + return html` +
          + ${this.renderButton( + msg("Add"), + EVENT_ADD_SELECTED, + this.addActive, + "fa-angle-right", + )} + ${this.selectAll + ? html` + ${this.renderButton( + msg("Add All Available"), + EVENT_ADD_ALL, + this.addAllActive, + "fa-angle-double-right", + )} + ${this.renderButton( + msg("Remove All Available"), + EVENT_REMOVE_ALL, + this.removeAllActive, + "fa-angle-double-left", + )} + ` + : nothing} + ${this.renderButton( + msg("Remove"), + EVENT_REMOVE_SELECTED, + this.removeActive, + "fa-angle-left", + )} + ${this.deleteAll + ? html`${this.renderButton( + msg("Remove All"), + EVENT_DELETE_ALL, + this.enableDeleteAll, + "fa-times", + )}` + : nothing} +
          + `; + } +} + +export default AkDualSelectControls; diff --git a/web/src/elements/ak-dual-select/components/ak-dual-select-selected-pane.ts b/web/src/elements/ak-dual-select/components/ak-dual-select-selected-pane.ts new file mode 100644 index 0000000000..981bef2fd2 --- /dev/null +++ b/web/src/elements/ak-dual-select/components/ak-dual-select-selected-pane.ts @@ -0,0 +1,139 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import { map } from "lit/directives/map.js"; + +import { listStyles, selectedPaneStyles } from "./styles.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFDualListSelector from "@patternfly/patternfly/components/DualListSelector/dual-list-selector.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { EVENT_REMOVE_ONE } from "../constants"; +import type { DualSelectPair } from "../types"; + +const styles = [PFBase, PFButton, PFDualListSelector, listStyles, selectedPaneStyles]; + +const hostAttributes = [ + ["aria-labelledby", "dual-list-selector-selected-pane-status"], + ["aria-multiselectable", "true"], + ["role", "listbox"], +]; + +/** + * @element ak-dual-select-available-panel + * + * The "selected options" or "right" pane in a dual-list multi-select. It receives from its parent + * a list of the selected options, and maintains an internal list of objects selected to move. + * + * @fires ak-dual-select-selected-move-changed - When the list of "to move" entries changed. + * Includes the current `toMove` content. + * + * @fires ak-dual-select-remove-one - Double-click with the element clicked on. + * + * It is not expected that the `ak-dual-select-selected-move-changed` will be used; instead, the + * attribute will be read by the parent when a control is clicked. + * + */ +@customElement("ak-dual-select-selected-pane") +export class AkDualSelectSelectedPane extends CustomEmitterElement(AKElement) { + static get styles() { + return styles; + } + + /* The array of key/value pairs that are in the selected list. ALL of them. */ + @property({ type: Array }) + readonly selected: DualSelectPair[] = []; + + /* + * This is the only mutator for this object. It collects the list of objects the user has + * clicked on *in this pane*. It is explicitly marked as "public" to emphasize that the parent + * orchestrator for the dual-select widget can and will access it to get the list of keys to be + * moved (removed) if the user so requests. + * + */ + @state() + public toMove: Set = new Set(); + + constructor() { + super(); + this.onClick = this.onClick.bind(this); + this.onMove = this.onMove.bind(this); + } + + connectedCallback() { + super.connectedCallback(); + hostAttributes.forEach(([attr, value]) => { + if (!this.hasAttribute(attr)) { + this.setAttribute(attr, value); + } + }); + } + + clearMove() { + this.toMove = new Set(); + } + + onClick(key: string) { + if (this.toMove.has(key)) { + this.toMove.delete(key); + } else { + this.toMove.add(key); + } + this.dispatchCustomEvent( + "ak-dual-select-selected-move-changed", + Array.from(this.toMove.values()).sort(), + ); + this.dispatchCustomEvent("ak-dual-select-move"); + // Necessary because updating a map won't trigger a state change + this.requestUpdate(); + } + + onMove(key: string) { + this.toMove.delete(key); + this.dispatchCustomEvent(EVENT_REMOVE_ONE, key); + this.requestUpdate(); + } + + get moveable() { + return Array.from(this.toMove.values()); + } + + render() { + return html` +
          +
            + ${map(this.selected, ([key, label]) => { + const selected = classMap({ + "pf-m-selected": this.toMove.has(key), + }); + return html`
          • this.onClick(key)} + @dblclick=${() => this.onMove(key)} + role="option" + data-ak-key=${key} + tabindex="-1" + > +
            + + + ${label} +
            +
          • `; + })} +
          +
          + `; + } +} + +export default AkDualSelectSelectedPane; diff --git a/web/src/elements/ak-dual-select/components/ak-pagination.ts b/web/src/elements/ak-dual-select/components/ak-pagination.ts new file mode 100644 index 0000000000..21065549ee --- /dev/null +++ b/web/src/elements/ak-dual-select/components/ak-pagination.ts @@ -0,0 +1,94 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { msg, str } from "@lit/localize"; +import { css, html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFPagination from "@patternfly/patternfly/components/Pagination/pagination.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import type { BasePagination } from "../types"; + +const styles = [ + PFBase, + PFButton, + PFPagination, + css` + :host([theme="dark"]) .pf-c-pagination__nav-control .pf-c-button { + color: var(--pf-c-button--m-plain--disabled--Color); + --pf-c-button--disabled--Color: var(--pf-c-button--m-plain--Color); + } + :host([theme="dark"]) .pf-c-pagination__nav-control .pf-c-button:disabled { + color: var(--pf-c-button--disabled--Color); + } + `, +]; + +@customElement("ak-pagination") +export class AkPagination extends CustomEmitterElement(AKElement) { + static get styles() { + return styles; + } + + @property({ attribute: false }) + pages?: BasePagination; + + constructor() { + super(); + this.onClick = this.onClick.bind(this); + } + + onClick(nav: number | undefined) { + this.dispatchCustomEvent("ak-pagination-nav-to", nav ?? 0); + } + + render() { + return this.pages + ? html`
          +
          +
          +
          + + ${msg( + str`${this.pages?.startIndex} - ${this.pages?.endIndex} of ${this.pages?.count}`, + )} + +
          +
          + +
          +
          ` + : nothing; + } +} + +export default AkPagination; diff --git a/web/src/elements/ak-dual-select/components/ak-search-bar.ts b/web/src/elements/ak-dual-select/components/ak-search-bar.ts new file mode 100644 index 0000000000..dafc149941 --- /dev/null +++ b/web/src/elements/ak-dual-select/components/ak-search-bar.ts @@ -0,0 +1,69 @@ +import { AKElement } from "@goauthentik/elements/Base"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { createRef, ref } from "lit/directives/ref.js"; +import type { Ref } from "lit/directives/ref.js"; + +import { globalVariables, searchStyles } from "./search.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import type { SearchbarEvent } from "../types"; + +const styles = [PFBase, globalVariables, searchStyles]; + +@customElement("ak-search-bar") +export class AkSearchbar extends CustomEmitterElement(AKElement) { + static get styles() { + return styles; + } + + @property({ type: String, reflect: true }) + value = ""; + + /** + * If you're using more than one search, this token can help listeners distinguishing between + * those searches. Lit's own helpers sometimes erase the source and current targets. + */ + @property({ type: String }) + name = ""; + + input: Ref = createRef(); + + constructor() { + super(); + this.onChange = this.onChange.bind(this); + } + + onChange(_event: Event) { + if (this.input.value) { + this.value = this.input.value.value; + } + this.dispatchCustomEvent("ak-search", { + source: this.name, + value: this.value, + }); + } + + render() { + return html` +
          +
          + +
          +
          + `; + } +} + +export default AkSearchbar; diff --git a/web/src/elements/ak-dual-select/components/search.css.ts b/web/src/elements/ak-dual-select/components/search.css.ts new file mode 100644 index 0000000000..1fa11fdf6c --- /dev/null +++ b/web/src/elements/ak-dual-select/components/search.css.ts @@ -0,0 +1,190 @@ +import { css } from "lit"; + +// The `host` information for the Patternfly dual list selector came with some default settings that +// we do not want in a web component. By isolating what we *really* use into this collection here, +// we get all the benefits of Patternfly without having to wrestle without also having to counteract +// those default settings. + +export const globalVariables = css` + :host { + --pf-c-text-input-group--BackgroundColor: var(--pf-global--BackgroundColorg--100); + --pf-c-text-input-group--Color: var(--pf-global--Color--dark-100); + + --pf-c-text-input-group__text--before--BorderWidth: var(--pf-global--BorderWidth--sm); + --pf-c-text-input-group__text--before--BorderColor: var(--pf-global--BorderColor--300); + + --pf-c-text-input-group__text--after--BorderBottomWidth: var(--pf-global--BorderWidth--sm); + --pf-c-text-input-group__text--after--BorderBottomColor: var(--pf-global--BorderColor--200); + + --pf-c-text-input-group--hover__text--after--BorderBottomColor: var( + --pf-global--primary-color--100 + ); + --pf-c-text-input-group__text--focus-within--after--BorderBottomWidth: var( + --pf-global--BorderWidth--md + ); + --pf-c-text-input-group__text--focus-within--after--BorderBottomColor: var( + --pf-global--primary-color--100 + ); + --pf-c-text-input-group__main--first-child--not--text-input--MarginLeft: var( + --pf-global--spacer--sm + ); + --pf-c-text-input-group__main--m-icon__text-input--PaddingLeft: var( + --pf-global--spacer--xl + ); + --pf-c-text-input-group__main--RowGap: var(--pf-global--spacer--xs); + --pf-c-text-input-group__main--ColumnGap: var(--pf-global--spacer--sm); + --pf-c-text-input-group--c-chip-group__main--PaddingTop: var(--pf-global--spacer--xs); + --pf-c-text-input-group--c-chip-group__main--PaddingRight: var(--pf-global--spacer--xs); + --pf-c-text-input-group--c-chip-group__main--PaddingBottom: var(--pf-global--spacer--xs); + --pf-c-text-input-group__text-input--PaddingTop: var(--pf-global--spacer--form-element); + --pf-c-text-input-group__text-input--PaddingRight: var(--pf-global--spacer--sm); + --pf-c-text-input-group__text-input--PaddingBottom: var(--pf-global--spacer--form-element); + --pf-c-text-input-group__text-input--PaddingLeft: var(--pf-global--spacer--sm); + --pf-c-text-input-group__text-input--MinWidth: 12ch; + --pf-c-text-input-group__text-input--m-hint--Color: var(--pf-global--Color--dark-200); + --pf-c-text-input-group--placeholder--Color: var(--pf-global--Color--dark-200); + --pf-c-text-input-group__icon--Left: var(--pf-global--spacer--sm); + --pf-c-text-input-group__icon--Color: var(--pf-global--Color--200); + --pf-c-text-input-group__text--hover__icon--Color: var(--pf-global--Color--100); + --pf-c-text-input-group__icon--TranslateY: -50%; + --pf-c-text-input-group__utilities--MarginRight: var(--pf-global--spacer--sm); + --pf-c-text-input-group__utilities--MarginLeft: var(--pf-global--spacer--xs); + --pf-c-text-input-group__utilities--child--MarginLeft: var(--pf-global--spacer--xs); + --pf-c-text-input-group__utilities--c-button--PaddingRight: var(--pf-global--spacer--xs); + --pf-c-text-input-group__utilities--c-button--PaddingLeft: var(--pf-global--spacer--xs); + --pf-c-text-input-group--m-disabled--Color: var(--pf-global--disabled-color--100); + --pf-c-text-input-group--m-disabled--BackgroundColor: var(--pf-global--disabled-color--300); + } + + :host([theme="dark"]) { + --pf-c-text-input-group--BackgroundColor: var(--ak-dark-background-light); + --pf-c-text-input-group--Color: var(--ak-dark-foreground); + + --pf-c-text-input-group__text--before--BorderColor: var(--ak-dark-background-lighter); + --pf-c-text-input-group__text--before--BorderWidth: 0; + + --pf-c-text-input-group--m-disabled--Color: var(--pf-global--disabled-color--300); + --pf-c-text-input-group--m-disabled--BackgroundColor: var(--pf-global--disabled-color--200); + + --pf-c-text-input-group__text--before--BorderBottomColor: var( + --pf-global--BorderColor--200 + ); + } +`; + +export const searchStyles = css` + i.fa, + i.fas, + i.far, + i.fal, + i.fab { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + } + + i.fa-search:before { + content: "\f002"; + } + + .fa, + .fas { + position: relative; + font-family: "Font Awesome 5 Free"; + font-weight: 900; + } + + i.fa-fw { + text-align: center; + width: 1.25em; + } + + .pf-c-text-input-group { + position: relative; + display: flex; + width: 100%; + color: var(--pf-c-text-input-group--Color, inherit); + background-color: var(--pf-c-text-input-group--BackgroundColor); + } + + .pf-c-text-input-group__main { + display: flex; + flex: 1; + flex-wrap: wrap; + gap: var(--pf-c-text-input-group__main--RowGap) + var(--pf-c-text-input-group__main--ColumnGap); + min-width: 0; + } + + .pf-c-text-input-group__main.pf-m-icon { + --pf-c-text-input-group__text-input--PaddingLeft: var( + --pf-c-text-input-group__main--m-icon__text-input--PaddingLeft + ); + } + .pf-c-text-input-group__text { + display: inline-grid; + grid-template-columns: 1fr; + grid-template-areas: "text-input"; + flex: 1; + z-index: 0; + } + + .pf-c-text-input-group__text::before { + border-width: var(--pf-c-text-input-group__text--before--BorderWidth); + border-color: var(--pf-c-text-input-group__text--before--BorderColor); + border-bottom-color: var(--pf-c-text-input-group__text--after--BorderBottomColor); + border-bottom-width: var(--pf-c-text-input-group__text--after--BorderBottomWidth); + border-style: solid; + } + + .pf-c-text-input-group__text::after { + border-bottom: var(--pf-c-text-input-group__text--after--BorderBottomWidth) solid + var(--pf-c-text-input-group__text--after--BorderBottomColor); + } + + .pf-c-text-input-group__text::before, + .pf-c-text-input-group__text::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + content: ""; + z-index: 2; + } + + .pf-c-text-input-group__icon { + z-index: 4; + position: absolute; + top: 50%; + left: var(--pf-c-text-input-group__icon--Left); + color: var(--pf-c-text-input-group__icon--Color); + transform: translateY(var(--pf-c-text-input-group__icon--TranslateY)); + } + + .pf-c-text-input-group__text-input, + .pf-c-text-input-group__text-input.pf-m-hint { + grid-area: text-input; + } + + .pf-c-text-input-group__text-input { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + position: relative; + width: 100%; + color: var(--pf-c-text-input-group--Color); + background-color: var(--pf-c-text-input-group--BackgroundColor); + min-width: var(--pf-c-text-input-group__text-input--MinWidth); + padding: var(--pf-c-text-input-group__text-input--PaddingTop) + var(--pf-c-text-input-group__text-input--PaddingRight) + var(--pf-c-text-input-group__text-input--PaddingBottom) + var(--pf-c-text-input-group__text-input--PaddingLeft); + border: 0; + } +`; diff --git a/web/src/elements/ak-dual-select/components/styles.css.ts b/web/src/elements/ak-dual-select/components/styles.css.ts new file mode 100644 index 0000000000..94959d059c --- /dev/null +++ b/web/src/elements/ak-dual-select/components/styles.css.ts @@ -0,0 +1,219 @@ +import { css } from "lit"; + +// The `host` information for the Patternfly dual list selector came with some default settings that +// we do not want in a web component. By isolating what we *really* use into this collection here, +// we get all the benefits of Patternfly without having to wrestle without also having to counteract +// those default settings. + +export const globalVariables = css` + :host { + --pf-c-dual-list-selector--GridTemplateColumns--pane--MinMax--min: 12.5rem; + --pf-c-dual-list-selector--GridTemplateColumns--pane--MinMax--max: 28.125rem; + --pf-c-dual-list-selector__header--MarginBottom: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__title-text--FontWeight: var(--pf-global--FontWeight--bold); + --pf-c-dual-list-selector__tools--MarginBottom: var(--pf-global--spacer--md); + --pf-c-dual-list-selector__tools-filter--tools-actions--MarginLeft: var( + --pf-global--spacer--sm + ); + --pf-c-dual-list-selector__menu--BorderWidth: var(--pf-global--BorderWidth--sm); + --pf-c-dual-list-selector__menu--BorderColor: var(--pf-global--BorderColor--100); + --pf-c-dual-list-selector__menu--MinHeight: 12.5rem; + --pf-c-dual-list-selector__menu--MaxHeight: 20rem; + --pf-c-dual-list-selector__list-item-row--FontSize: var(--pf-global--FontSize--sm); + --pf-c-dual-list-selector__list-item-row--BackgroundColor: transparent; + --pf-c-dual-list-selector__list-item-row--hover--BackgroundColor: var( + --pf-global--BackgroundColor--light-300 + ); + --pf-c-dual-list-selector__list-item-row--focus-within--BackgroundColor: var( + --pf-global--BackgroundColor--light-300 + ); + --pf-c-dual-list-selector__list-item-row--m-selected--BackgroundColor: var( + --pf-global--BackgroundColor--light-300 + ); + --pf-c-dual-list-selector__list-item--m-ghost-row--BackgroundColor: var( + --pf-global--BackgroundColor--100 + ); + --pf-c-dual-list-selector__list-item--m-ghost-row--Opacity: 0.4; + --pf-c-dual-list-selector__item--PaddingTop: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__item--PaddingRight: var(--pf-global--spacer--md); + --pf-c-dual-list-selector__item--PaddingBottom: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__item--PaddingLeft: var(--pf-global--spacer--md); + --pf-c-dual-list-selector__item--m-expandable--PaddingLeft: 0; + --pf-c-dual-list-selector__item--indent--base: calc( + var(--pf-global--spacer--md) + var(--pf-global--spacer--sm) + + var(--pf-c-dual-list-selector__list-item-row--FontSize) + ); + --pf-c-dual-list-selector__item--nested-indent--base: calc( + var(--pf-c-dual-list-selector__item--indent--base) - var(--pf-global--spacer--md) + ); + --pf-c-dual-list-selector__draggable--item--PaddingLeft: var(--pf-global--spacer--xs); + --pf-c-dual-list-selector__item-text--Color: var(--pf-global--Color--100); + --pf-c-dual-list-selector__list-item-row--m-selected__text--Color: var( + --pf-global--active-color--100 + ); + --pf-c-dual-list-selector__list-item-row--m-selected__text--FontWeight: var( + --pf-global--FontWeight--bold + ); + --pf-c-dual-list-selector__list-item--m-disabled__item-text--Color: var( + --pf-global--disabled-color--100 + ); + --pf-c-dual-list-selector__status--MarginBottom: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__status-text--FontSize: var(--pf-global--FontSize--sm); + --pf-c-dual-list-selector__status-text--Color: var(--pf-global--Color--200); + --pf-c-dual-list-selector__controls--PaddingRight: var(--pf-global--spacer--md); + --pf-c-dual-list-selector__controls--PaddingLeft: var(--pf-global--spacer--md); + --pf-c-dual-list-selector__item-toggle--PaddingTop: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__item-toggle--PaddingRight: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__item-toggle--PaddingBottom: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__item-toggle--PaddingLeft: var(--pf-global--spacer--md); + --pf-c-dual-list-selector__item-toggle--MarginTop: calc(var(--pf-global--spacer--sm) * -1); + --pf-c-dual-list-selector__item-toggle--MarginBottom: calc( + var(--pf-global--spacer--sm) * -1 + ); + --pf-c-dual-list-selector__list__list__item-toggle--Left: 0; + --pf-c-dual-list-selector__list__list__item-toggle--TranslateX: -100%; + --pf-c-dual-list-selector__item-check--MarginRight: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__item-count--Marginleft: var(--pf-global--spacer--sm); + --pf-c-dual-list-selector__item--c-badge--m-read--BackgroundColor: var( + --pf-global--disabled-color--200 + ); + --pf-c-dual-list-selector__item-toggle-icon--Rotate: 0; + --pf-c-dual-list-selector__list-item--m-expanded__item-toggle-icon--Rotate: 90deg; + --pf-c-dual-list-selector__item-toggle-icon--Transition: var(--pf-global--Transition); + --pf-c-dual-list-selector__item-toggle-icon--MinWidth: var( + --pf-c-dual-list-selector__list-item-row--FontSize + ); + --pf-c-dual-list-selector__list-item--m-disabled__item-toggle-icon--Color: var( + --pf-global--disabled-color--200 + ); + + /* Unique to authentik */ + --pf-c-dual-list-selector--selection-desc--FontSize: var(--pf-global--FontSize--xs); + --pf-c-dual-list-selector--selection-desc--Color: var(--pf-global--Color--dark-200); + --pf-c-dual-list-selector__status--top-padding: var(--pf-global--spacer--xs); + --pf-c-dual-list-panels__gap: var(--pf-global--spacer--xs); + } + + :host([theme="dark"]) { + --pf-c-dual-list-selector__menu--BorderColor: var(--ak-dark-background-lighter); + --pf-c-dual-list-selector__item-text--Color: var(--ak-dark-foreground); + --pf-c-dual-list-selector__list-item-row--BackgroundColor: var( + --ak-dark-background-light-ish + ); + --pf-c-dual-list-selector__list-item-row--hover--BackgroundColor: var( + --ak-dark-background-lighter; + ); + --pf-c-dual-list-selector__list-item-row--hover--BackgroundColor: var( + --pf-global--BackgroundColor--400 + ); + } +`; + +export const mainStyles = css` + :host { + --pf-c-dual-list-selector--GridTemplateColumns--pane--MinMax--min: 12.5rem; + --pf-c-dual-list-selector--GridTemplateColumns--pane--MinMax--max: 28.125rem; + } + :host { + display: block grid; + } + + .pf-c-dual-list-selector__title-text { + font-weight: var(--pf-c-dual-list-selector__title-text--FontWeight); + } + + .pf-c-dual-list-selector__status { + padding-top: var(--pf-c-dual-list-selector__status--top-padding); + } + + .pf-c-dual-list-selector__status-text { + font-size: var(--pf-c-dual-list-selector__status-text--FontSize); + color: var(--pf-c-dual-list-selector__status-text--Color); + } + + .ak-dual-list-selector { + display: grid; + grid-template-columns: minmax(0, 1fr) min-content minmax(0, 1fr); + } + + .ak-available-pane, + .ak-selected-pane { + display: grid; + grid-template-rows: auto auto auto 1fr auto; + gap: var(--pf-c-dual-list-panels__gap); + max-width: 100%; + overflow: hidden; + } + + ak-dual-select-controls { + height: 100%; + } +`; + +export const listStyles = css` + :host { + display: block; + overflow: hidden; + max-width: 100%; + } + + .pf-c-dual-list-selector__menu { + max-width: 100%; + height: 100%; + } + + .pf-c-dual-list-selector__list { + max-width: 100%; + display: block; + } + + .pf-c-dual-list-selector__item { + padding: 0.25rem; + width: auto; + } + + .pf-c-dual-list-selector__item-text { + user-select: none; + flex-grow: 0; + } + + .pf-c-dual-list-selector__item-text .selection-main { + color: var(--pf-c-dual-list-selector__item-text--Color); + } + + .pf-c-dual-list-selector__item-text .selection-main:hover { + color: var(--pf-c-dual-list-selector__item-text--Color); + } + + .pf-c-dual-list-selector__item-text .selection-desc { + font-size: var(--pf-c-dual-list-selector--selection-desc--FontSize); + color: var(--pf-c-dual-list-selector--selection-desc--Color); + } +`; + +export const selectedPaneStyles = css` + input[type="checkbox"][readonly] { + pointer-events: none; + } +`; + +export const availablePaneStyles = css` + .pf-c-dual-list-selector__item-text { + display: grid; + grid-template-columns: 1fr auto; + } + + .pf-c-dual-list-selector__item-text .pf-c-dual-list-selector__item-text-selected-indicator { + display: grid; + justify-content: center; + align-content: center; + } + + .pf-c-dual-list-selector__item-text i { + display: inline-block; + padding-left: 1rem; + font-weight: 200; + color: var(--pf-c-dual-list-selector--selection-desc--Color); + font-size: var(--pf-global--FontSize--xs); + } +`; diff --git a/web/src/elements/ak-dual-select/constants.ts b/web/src/elements/ak-dual-select/constants.ts new file mode 100644 index 0000000000..8e7db5369d --- /dev/null +++ b/web/src/elements/ak-dual-select/constants.ts @@ -0,0 +1,7 @@ +export const EVENT_ADD_SELECTED = "ak-dual-select-add"; +export const EVENT_REMOVE_SELECTED = "ak-dual-select-remove"; +export const EVENT_ADD_ALL = "ak-dual-select-add-all"; +export const EVENT_REMOVE_ALL = "ak-dual-select-remove-all"; +export const EVENT_DELETE_ALL = "ak-dual-select-remove-everything"; +export const EVENT_ADD_ONE = "ak-dual-select-add-one"; +export const EVENT_REMOVE_ONE = "ak-dual-select-remove-one"; diff --git a/web/src/elements/ak-dual-select/index.ts b/web/src/elements/ak-dual-select/index.ts new file mode 100644 index 0000000000..a5b14dabc3 --- /dev/null +++ b/web/src/elements/ak-dual-select/index.ts @@ -0,0 +1,7 @@ +import { AkDualSelect } from "./ak-dual-select"; +import "./ak-dual-select"; +import { AkDualSelectProvider } from "./ak-dual-select-provider"; +import "./ak-dual-select-provider"; + +export { AkDualSelect, AkDualSelectProvider }; +export default AkDualSelect; diff --git a/web/src/elements/ak-dual-select/stories/ak-dual-select-available-pane.stories.ts b/web/src/elements/ak-dual-select/stories/ak-dual-select-available-pane.stories.ts new file mode 100644 index 0000000000..e0f9a0b0e0 --- /dev/null +++ b/web/src/elements/ak-dual-select/stories/ak-dual-select-available-pane.stories.ts @@ -0,0 +1,115 @@ +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta, StoryObj } from "@storybook/web-components"; +import { slug } from "github-slugger"; + +import { TemplateResult, html } from "lit"; + +import "../components/ak-dual-select-available-pane"; +import { AkDualSelectAvailablePane } from "../components/ak-dual-select-available-pane"; +import "./sb-host-provider"; + +const metadata: Meta = { + title: "Elements / Dual Select / Available Items Pane", + component: "ak-dual-select-available-pane", + parameters: { + docs: { + description: { + component: "The vertical panel separating two dual-select elements.", + }, + }, + }, + argTypes: { + options: { + type: "string", + description: "An array of [key, label] pairs of what to show", + }, + selected: { + type: "string", + description: "An array of [key] of what has already been selected", + }, + toMove: { + type: "string", + description: "An array of items which are to be moved to the receiving pane.", + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
          + + + ${testItem} +

          Messages received from the button:

          +
            +
            `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleMoveChanged = (result: any) => { + const target = document.querySelector("#action-button-message-pad"); + target!.innerHTML = ""; + result.detail.forEach((key: string) => { + target!.append(new DOMParser().parseFromString(`
          • ${key}
          • `, "text/xml").firstChild!); + }); +}; + +window.addEventListener("ak-dual-select-available-move-changed", handleMoveChanged); + +type Story = StoryObj; + +const goodForYou = [ + "Apple", + "Arrowroot", + "Artichoke", + "Arugula", + "Asparagus", + "Avocado", + "Bamboo", + "Banana", + "Basil", + "Beet Root", + "Blackberry", + "Blueberry", + "Bok Choy", + "Broccoli", + "Brussels sprouts", + "Cabbage", + "Cantaloupes", + "Carrot", + "Cauliflower", +]; + +const goodForYouPairs = goodForYou.map((key) => [slug(key), key]); + +export const Default: Story = { + render: () => + container( + html` `, + ), +}; + +const someSelected = new Set([ + goodForYouPairs[2][0], + goodForYouPairs[8][0], + goodForYouPairs[14][0], +]); + +export const SomeSelected: Story = { + render: () => + container( + html` `, + ), +}; diff --git a/web/src/elements/ak-dual-select/stories/ak-dual-select-controls.stories.ts b/web/src/elements/ak-dual-select/stories/ak-dual-select-controls.stories.ts new file mode 100644 index 0000000000..ad89d535bc --- /dev/null +++ b/web/src/elements/ak-dual-select/stories/ak-dual-select-controls.stories.ts @@ -0,0 +1,101 @@ +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta, StoryObj } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +import "../components/ak-dual-select-controls"; +import { AkDualSelectControls } from "../components/ak-dual-select-controls"; + +const metadata: Meta = { + title: "Elements / Dual Select / Control Panel", + component: "ak-dual-select-controls", + parameters: { + docs: { + description: { + component: "The vertical panel separating two dual-select elements.", + }, + }, + }, + argTypes: { + addActive: { + type: "boolean", + description: + "Highlighted if the sample panel has something to move to the result panel.", + }, + removeActive: { + type: "boolean", + description: + "Highlighted if the result panel has something to move to the sample panel.", + }, + selectAll: { + type: "boolean", + description: "Enable if you want both the 'move all visible' buttons.", + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
            + + + ${testItem} +

            Messages received from the button:

            +
              +
              `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const displayMessage = (result: any) => { + const doc = new DOMParser().parseFromString(`
            • Event: ${result}
            • `, "text/xml"); + const target = document.querySelector("#action-button-message-pad"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + target!.appendChild(doc.firstChild!); +}; + +window.addEventListener("ak-dual-select-add", () => displayMessage("add")); +window.addEventListener("ak-dual-select-remove", () => displayMessage("remove")); +window.addEventListener("ak-dual-select-add-all", () => displayMessage("add all")); +window.addEventListener("ak-dual-select-remove-all", () => displayMessage("remove all")); + +type Story = StoryObj; + +export const Default: Story = { + render: () => container(html` `), +}; + +export const AddActive: Story = { + render: () => container(html` `), +}; + +export const RemoveActive: Story = { + render: () => + container(html` `), +}; + +export const AddAllActive: Story = { + render: () => + container( + html` `, + ), +}; + +export const RemoveAllActive: Story = { + render: () => + container( + html` `, + ), +}; diff --git a/web/src/elements/ak-dual-select/stories/ak-dual-select-master.stories.ts b/web/src/elements/ak-dual-select/stories/ak-dual-select-master.stories.ts new file mode 100644 index 0000000000..2c66139805 --- /dev/null +++ b/web/src/elements/ak-dual-select/stories/ak-dual-select-master.stories.ts @@ -0,0 +1,156 @@ +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta, StoryObj } from "@storybook/web-components"; +import { slug } from "github-slugger"; + +import { LitElement, TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import { Pagination } from "@goauthentik/api"; + +import "../ak-dual-select"; +import { AkDualSelect } from "../ak-dual-select"; +import type { DualSelectPair } from "../types"; + +const goodForYouRaw = ` +Apple, Arrowroot, Artichoke, Arugula, Asparagus, Avocado, Bamboo, Banana, Basil, Beet Root, +Blackberry, Blueberry, Bok Choy, Broccoli, Brussels sprouts, Cabbage, Cantaloupes, Carrot, +Cauliflower, Celery, Chayote, Chives, Cilantro, Coconut, Collard Greens, Corn, Cucumber, Daikon, +Date, Dill, Eggplant, Endive, Fennel, Fig, Garbanzo Bean, Garlic, Ginger, Gourds, Grape, Guava, +Honeydew, Horseradish, Iceberg Lettuce, Jackfruit, Jicama, Kale, Kangkong, Kiwi, Kohlrabi, Leek, +Lentils, Lychee, Macadamia, Mango, Mushroom, Mustard, Nectarine, Okra, Onion, Papaya, Parsley, +Parsley root, Parsnip, Passion Fruit, Peach, Pear, Peas, Peppers, Persimmon, Pimiento, Pineapple, +Plum, Plum, Pomegranate, Potato, Pumpkin, Radicchio, Radish, Raspberry, Rhubarb, Romaine Lettuce, +Rosemary, Rutabaga, Shallot, Soybeans, Spinach, Squash, Strawberries, Sweet potato, Swiss Chard, +Thyme, Tomatillo, Tomato, Turnip, Waterchestnut, Watercress, Watermelon, Yams +`; + +const keyToPair = (key: string): DualSelectPair => [slug(key), key]; +const goodForYou: DualSelectPair[] = goodForYouRaw + .split("\n") + .join(" ") + .split(",") + .map((a: string) => a.trim()) + .map(keyToPair); + +const metadata: Meta = { + title: "Elements / Dual Select / Dual Select With Pagination", + component: "ak-dual-select", + parameters: { + docs: { + description: { + component: "The three-panel assembly", + }, + }, + }, + argTypes: { + options: { + type: "string", + description: "An array of [key, label] pairs of what to show", + }, + selected: { + type: "string", + description: "An array of [key] of what has already been selected", + }, + pages: { + type: "string", + description: "An authentik pagination object.", + }, + }, +}; + +export default metadata; + +@customElement("ak-sb-fruity") +export class AkSbFruity extends LitElement { + @property({ type: Array }) + options: DualSelectPair[] = goodForYou; + + @property({ attribute: "page-length", type: Number }) + pageLength = 20; + + @state() + page: Pagination; + + constructor() { + super(); + this.page = { + count: this.options.length, + current: 1, + startIndex: 1, + endIndex: this.options.length > this.pageLength ? this.pageLength : this.options.length, + next: this.options.length > this.pageLength ? 2 : 0, + previous: 0, + totalPages: Math.ceil(this.options.length / this.pageLength), + }; + this.onNavigation = this.onNavigation.bind(this); + this.addEventListener("ak-pagination-nav-to", this.onNavigation); + } + + onNavigation(evt: Event) { + const current: number = (evt as CustomEvent).detail; + const index = current - 1; + if (index * this.pageLength > this.options.length) { + console.warn( + `Attempted to index from ${index} for options length ${this.options.length}`, + ); + return; + } + const endCount = this.pageLength * (index + 1); + const endIndex = Math.min(endCount, this.options.length); + + this.page = { + ...this.page, + current, + startIndex: this.pageLength * index + 1, + endIndex, + next: (index + 1) * this.pageLength > this.options.length ? 0 : current + 1, + previous: index, + }; + } + + get pageoptions() { + return this.options.slice( + this.pageLength * (this.page.current - 1), + this.pageLength * this.page.current, + ); + } + + render() { + return html``; + } +} + +const container = (testItem: TemplateResult) => + html`
              + + + ${testItem} +

              Messages received from the button:

              +
              +
              `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleMoveChanged = (result: any) => { + const target = document.querySelector("#action-button-message-pad"); + target!.innerHTML = ""; + // @ts-ignore + target!.append(result.detail.value.map(([k, _]) => k).join(", ")); +}; + +window.addEventListener("change", handleMoveChanged); + +type Story = StoryObj; + +export const Default: Story = { + render: () => container(html` `), +}; diff --git a/web/src/elements/ak-dual-select/stories/ak-dual-select-search.stories.ts b/web/src/elements/ak-dual-select/stories/ak-dual-select-search.stories.ts new file mode 100644 index 0000000000..49bf52fc54 --- /dev/null +++ b/web/src/elements/ak-dual-select/stories/ak-dual-select-search.stories.ts @@ -0,0 +1,70 @@ +import "@goauthentik/elements/messages/MessageContainer"; +import { debounce } from "@goauthentik/elements/utils/debounce"; +import { Meta, StoryObj } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +import "../components/ak-search-bar"; +import { AkSearchbar } from "../components/ak-search-bar"; + +const metadata: Meta = { + title: "Elements / Dual Select / Search Bar", + component: "ak-dual-select-search", + parameters: { + docs: { + description: { + component: "A search input bar", + }, + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
              + + + ${testItem} +

              Messages received from the button:

              +
              +
              +
              `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const displayMessage = (result: any) => { + const doc = new DOMParser().parseFromString(`

              Content: ${result}

              `, "text/xml"); + const target = document.querySelector("#action-button-message-pad"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + target!.replaceChildren(doc.firstChild!); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const displayMessage2 = (result: any) => { + console.debug("Huh."); + const doc = new DOMParser().parseFromString(`

              Behavior: ${result}

              `, "text/xml"); + const target = document.querySelector("#action-button-message-pad-2"); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + target!.replaceChildren(doc.firstChild!); +}; + +const displayMessage2b = debounce(displayMessage2, 250); + +window.addEventListener("input", (event: Event) => { + const message = (event.target as HTMLInputElement | undefined)?.value ?? "-- undefined --"; + displayMessage(message); + displayMessage2b(message); +}); + +type Story = StoryObj; + +export const Default: Story = { + render: () => container(html` `), +}; diff --git a/web/src/elements/ak-dual-select/stories/ak-dual-select-selected-pane.stories.ts b/web/src/elements/ak-dual-select/stories/ak-dual-select-selected-pane.stories.ts new file mode 100644 index 0000000000..4dc6ef9c3b --- /dev/null +++ b/web/src/elements/ak-dual-select/stories/ak-dual-select-selected-pane.stories.ts @@ -0,0 +1,96 @@ +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta, StoryObj } from "@storybook/web-components"; +import { slug } from "github-slugger"; + +import { TemplateResult, html } from "lit"; + +import "../components/ak-dual-select-selected-pane"; +import { AkDualSelectSelectedPane } from "../components/ak-dual-select-selected-pane"; +import "./sb-host-provider"; + +const metadata: Meta = { + title: "Elements / Dual Select / Selected Items Pane", + component: "ak-dual-select-selected-pane", + parameters: { + docs: { + description: { + component: "The vertical panel separating two dual-select elements.", + }, + }, + }, + argTypes: { + // @ts-ignore + options: { + type: "string", + description: "An array of [key, label] pairs of what to show", + }, + toMove: { + type: "string", + description: "An array of items which are to be moved to the receiving pane.", + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
              + + + ${testItem} +

              Messages received from the button:

              +
                +
                `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleMoveChanged = (result: any) => { + const target = document.querySelector("#action-button-message-pad"); + target!.innerHTML = ""; + result.detail.forEach((key: string) => { + target!.append(new DOMParser().parseFromString(`
              • ${key}
              • `, "text/xml").firstChild!); + }); +}; + +window.addEventListener("ak-dual-select-selected-move-changed", handleMoveChanged); + +type Story = StoryObj; + +const goodForYou = [ + "Apple", + "Arrowroot", + "Artichoke", + "Arugula", + "Asparagus", + "Avocado", + "Bamboo", + "Banana", + "Basil", + "Beet Root", + "Blackberry", + "Blueberry", + "Bok Choy", + "Broccoli", + "Brussels sprouts", + "Cabbage", + "Cantaloupes", + "Carrot", + "Cauliflower", +]; + +const goodForYouPairs = goodForYou.map((key) => [slug(key), key]); + +export const Default: Story = { + render: () => + container( + html` `, + ), +}; diff --git a/web/src/elements/ak-dual-select/stories/ak-dual-select.stories.ts b/web/src/elements/ak-dual-select/stories/ak-dual-select.stories.ts new file mode 100644 index 0000000000..67c0cb3d03 --- /dev/null +++ b/web/src/elements/ak-dual-select/stories/ak-dual-select.stories.ts @@ -0,0 +1,93 @@ +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta, StoryObj } from "@storybook/web-components"; +import { slug } from "github-slugger"; + +import { TemplateResult, html } from "lit"; + +import "../ak-dual-select"; +import { AkDualSelect } from "../ak-dual-select"; + +const metadata: Meta = { + title: "Elements / Dual Select / Dual Select", + component: "ak-dual-select", + parameters: { + docs: { + description: { + component: "The three-panel assembly", + }, + }, + }, + argTypes: { + options: { + type: "string", + description: "An array of [key, label] pairs of what to show", + }, + selected: { + type: "string", + description: "An array of [key] of what has already been selected", + }, + pages: { + type: "string", + description: "An authentik pagination object.", + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
                + + + ${testItem} +

                Messages received from the button:

                +
                  +
                  `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleMoveChanged = (result: any) => { + const target = document.querySelector("#action-button-message-pad"); + target!.innerHTML = ""; + result.detail.value.forEach((key: string) => { + target!.append(new DOMParser().parseFromString(`
                • ${key}
                • `, "text/xml").firstChild!); + }); +}; + +window.addEventListener("change", handleMoveChanged); + +type Story = StoryObj; + +const goodForYou = [ + "Apple", + "Arrowroot", + "Artichoke", + "Arugula", + "Asparagus", + "Avocado", + "Bamboo", + "Banana", + "Basil", + "Beet Root", + "Blackberry", + "Blueberry", + "Bok Choy", + "Broccoli", + "Brussels sprouts", + "Cabbage", + "Cantaloupes", + "Carrot", + "Cauliflower", +]; + +const goodForYouPairs = goodForYou.map((key) => [slug(key), key]); + +export const Default: Story = { + render: () => container(html` `), +}; diff --git a/web/src/elements/ak-dual-select/stories/ak-pagination.stories.ts b/web/src/elements/ak-dual-select/stories/ak-pagination.stories.ts new file mode 100644 index 0000000000..0ecbe68af9 --- /dev/null +++ b/web/src/elements/ak-dual-select/stories/ak-pagination.stories.ts @@ -0,0 +1,83 @@ +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta, StoryObj } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +import "../components/ak-pagination"; +import { AkPagination } from "../components/ak-pagination"; + +const metadata: Meta = { + title: "Elements / Dual Select / Pagination Control", + component: "ak-pagination", + parameters: { + docs: { + description: { + component: "The Pagination Control", + }, + }, + }, + argTypes: { + pages: { + type: "string", + description: "An authentik Pagination struct", + }, + }, +}; + +export default metadata; + +const container = (testItem: TemplateResult) => + html`
                  + + + ${testItem} +

                  Messages received from the button:

                  +
                    +
                    `; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleMoveChanged = (result: any) => { + console.debug(result); + const target = document.querySelector("#action-button-message-pad"); + target!.append( + new DOMParser().parseFromString( + `
                  • Request to move to page ${result.detail}
                  • `, + "text/xml", + ).firstChild!, + ); +}; + +window.addEventListener("ak-pagination-nav-to", handleMoveChanged); + +type Story = StoryObj; + +const pages = { + count: 44, + startIndex: 1, + endIndex: 20, + next: 2, + previous: 0, +}; + +export const Default: Story = { + render: () => container(html` `), +}; + +const morePages = { + count: 86, + startIndex: 21, + endIndex: 40, + next: 3, + previous: 1, +}; + +export const More: Story = { + render: () => container(html` `), +}; diff --git a/web/src/elements/ak-dual-select/stories/sb-host-provider.ts b/web/src/elements/ak-dual-select/stories/sb-host-provider.ts new file mode 100644 index 0000000000..985d9083dc --- /dev/null +++ b/web/src/elements/ak-dual-select/stories/sb-host-provider.ts @@ -0,0 +1,22 @@ +import { LitElement, html } from "lit"; +import { customElement } from "lit/decorators.js"; + +import { globalVariables } from "../components/styles.css"; + +/** + * @element sb-dual-select-host-provider + * + * A *very simple* wrapper which provides the CSS Custom Properties used by the components when + * being displayed in Storybook or Vite. Not needed for the parent widget since it provides these by itself. + */ + +@customElement("sb-dual-select-host-provider") +export class SbHostProvider extends LitElement { + static get styles() { + return globalVariables; + } + + render() { + return html``; + } +} diff --git a/web/src/elements/ak-dual-select/types.ts b/web/src/elements/ak-dual-select/types.ts new file mode 100644 index 0000000000..2e7cea13a0 --- /dev/null +++ b/web/src/elements/ak-dual-select/types.ts @@ -0,0 +1,26 @@ +import { TemplateResult } from "lit"; + +import { Pagination } from "@goauthentik/api"; + +// Key, Label (string or TemplateResult), (optional) string to sort by. If the sort string is +// missing, it will use the label, which doesn't always work for TemplateResults). +export type DualSelectPair = [string, string | TemplateResult, string?]; + +export type BasePagination = Pick< + Pagination, + "startIndex" | "endIndex" | "count" | "previous" | "next" +>; + +export type DataProvision = { + pagination: Pagination; + options: DualSelectPair[]; +}; + +export type DataProvider = (page: number, search?: string) => Promise; + +export interface SearchbarEvent extends CustomEvent { + detail: { + source: string; + value: string; + }; +} diff --git a/web/src/elements/ak-locale-context/ak-locale-context.ts b/web/src/elements/ak-locale-context/ak-locale-context.ts index 912d6b711a..c01c9c29fc 100644 --- a/web/src/elements/ak-locale-context/ak-locale-context.ts +++ b/web/src/elements/ak-locale-context/ak-locale-context.ts @@ -1,18 +1,15 @@ -import { EVENT_LOCALE_CHANGE } from "@goauthentik/common/constants"; -import { EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants"; -import { customEvent, isCustomEvent } from "@goauthentik/elements/utils/customEvents"; +import { EVENT_LOCALE_CHANGE, EVENT_LOCALE_REQUEST } from "@goauthentik/common/constants"; +import { customEvent } from "@goauthentik/elements/utils/customEvents"; import { LitElement, html } from "lit"; import { customElement, property } from "lit/decorators.js"; +import { WithBrandConfig } from "../Interface/brandProvider"; import { initializeLocalization } from "./configureLocale"; import type { LocaleGetter, LocaleSetter } from "./configureLocale"; -import { - DEFAULT_LOCALE, - autoDetectLanguage, - getBestMatchLocale, - localeCodeFromUrl, -} from "./helpers"; +import { DEFAULT_LOCALE, autoDetectLanguage, getBestMatchLocale } from "./helpers"; + +const LocaleContextBase = WithBrandConfig(LitElement); /** * A component to manage your locale settings. @@ -28,7 +25,7 @@ import { * @fires ak-locale-change - When a valid locale has been swapped in */ @customElement("ak-locale-context") -export class LocaleContext extends LitElement { +export class LocaleContext extends LocaleContextBase { /// @attribute The text representation of the current locale */ @property({ attribute: true, type: String }) locale = DEFAULT_LOCALE; @@ -59,38 +56,28 @@ export class LocaleContext extends LitElement { connectedCallback() { super.connectedCallback(); - const localeRequest = autoDetectLanguage(this.locale); - this.updateLocale(localeRequest); - window.addEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler); + this.updateLocale(); + window.addEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler as EventListener); } disconnectedCallback() { - window.removeEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler); + window.removeEventListener(EVENT_LOCALE_REQUEST, this.updateLocaleHandler as EventListener); super.disconnectedCallback(); } - updateLocaleHandler(ev: Event) { - if (!isCustomEvent(ev)) { - console.warn(`Received a non-custom event at EVENT_LOCALE_REQUEST: ${ev}`); - return; - } + updateLocaleHandler(ev: CustomEvent<{ locale: string }>) { console.debug("authentik/locale: Locale update request received."); this.updateLocale(ev.detail.locale); } - updateLocale(code: string) { - const urlCode = localeCodeFromUrl(this.param); - const requestedLocale = urlCode ? urlCode : code; - const locale = getBestMatchLocale(requestedLocale); + updateLocale(requestedLocale: string | undefined = undefined) { + const localeRequest = autoDetectLanguage(requestedLocale, this.brand?.defaultLocale); + const locale = getBestMatchLocale(localeRequest); if (!locale) { - console.warn(`authentik/locale: failed to find locale for code ${code}`); + console.warn(`authentik/locale: failed to find locale for code ${localeRequest}`); return; } locale.locale().then(() => { - console.debug(`authentik/locale: Loaded locale '${code}'`); - if (this.getLocale() === code) { - return; - } console.debug(`Setting Locale to ... ${locale.label()} (${locale.code})`); this.setLocale(locale.code).then(() => { window.setTimeout(this.notifyApplication, 0); diff --git a/web/src/elements/ak-locale-context/configureLocale.ts b/web/src/elements/ak-locale-context/configureLocale.ts index 21f244668e..ddf47c85b1 100644 --- a/web/src/elements/ak-locale-context/configureLocale.ts +++ b/web/src/elements/ak-locale-context/configureLocale.ts @@ -1,7 +1,6 @@ -import { sourceLocale, targetLocales } from "@goauthentik/app/locale-codes"; - import { configureLocalization } from "@lit/localize"; +import { sourceLocale, targetLocales } from "../../locale-codes"; import { getBestMatchLocale } from "./helpers"; type LocaleGetter = ReturnType["getLocale"]; diff --git a/web/src/elements/ak-locale-context/context.ts b/web/src/elements/ak-locale-context/context.ts index 9c9903bb33..e63b67e7dc 100644 --- a/web/src/elements/ak-locale-context/context.ts +++ b/web/src/elements/ak-locale-context/context.ts @@ -1,4 +1,4 @@ -import { createContext } from "@lit-labs/context"; +import { createContext } from "@lit/context"; export const localeContext = createContext("locale"); export default localeContext; diff --git a/web/src/elements/ak-locale-context/definitions.ts b/web/src/elements/ak-locale-context/definitions.ts index e920e85b11..018c9e2a16 100644 --- a/web/src/elements/ak-locale-context/definitions.ts +++ b/web/src/elements/ak-locale-context/definitions.ts @@ -46,6 +46,8 @@ const LOCALE_TABLE: LocaleRow[] = [ ["es", /^es([_-]|$)/i, () => msg("Spanish"), async () => await import("@goauthentik/locales/es")], ["de", /^de([_-]|$)/i, () => msg("German"), async () => await import("@goauthentik/locales/de")], ["fr", /^fr([_-]|$)/i, () => msg("French"), async () => await import("@goauthentik/locales/fr")], + ["ko", /^ko([_-]|$)/i, () => msg("Korean"), async () => await import("@goauthentik/locales/ko")], + ["nl", /^nl([_-]|$)/i, () => msg("Dutch"), async () => await import("@goauthentik/locales/nl")], ["pl", /^pl([_-]|$)/i, () => msg("Polish"), async () => await import("@goauthentik/locales/pl")], ["tr", /^tr([_-]|$)/i, () => msg("Turkish"), async () => await import("@goauthentik/locales/tr")], ["zh-Hant", /^zh[_-](HK|Hant)/i, () => msg("Chinese (traditional)"), async () => await import("@goauthentik/locales/zh-Hant")], diff --git a/web/src/elements/ak-locale-context/helpers.ts b/web/src/elements/ak-locale-context/helpers.ts index 51ef765095..751371485a 100644 --- a/web/src/elements/ak-locale-context/helpers.ts +++ b/web/src/elements/ak-locale-context/helpers.ts @@ -45,12 +45,13 @@ export function localeCodeFromUrl(param = "locale") { const isLocaleCandidate = (v: unknown): v is string => typeof v === "string" && v !== "" && v !== TOMBSTONE; -export function autoDetectLanguage(requestedCode?: string): string { +export function autoDetectLanguage(userReq = TOMBSTONE, brandReq = TOMBSTONE): string { const localeCandidates: string[] = [ - globalAK()?.locale ?? TOMBSTONE, localeCodeFromUrl("locale"), - requestedCode ?? TOMBSTONE, + userReq, window.navigator?.language ?? TOMBSTONE, + brandReq, + globalAK()?.locale ?? TOMBSTONE, DEFAULT_LOCALE, ].filter(isLocaleCandidate); diff --git a/web/src/elements/buttons/ModalButton.ts b/web/src/elements/buttons/ModalButton.ts index dfc594e2ce..1fb4a059af 100644 --- a/web/src/elements/buttons/ModalButton.ts +++ b/web/src/elements/buttons/ModalButton.ts @@ -1,5 +1,9 @@ +import { PFSize } from "@goauthentik/common/enums.js"; import { AKElement } from "@goauthentik/elements/Base"; -import { PFSize } from "@goauthentik/elements/Spinner"; +import { + ModalHideEvent, + ModalShowEvent, +} from "@goauthentik/elements/controllers/ModalOrchestrationController.js"; import { CSSResult, TemplateResult, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; @@ -65,22 +69,9 @@ export class ModalButton extends AKElement { ]; } - firstUpdated(): void { - if (this.handlerBound) return; - window.addEventListener("keyup", this.keyUpHandler); - this.handlerBound = true; - } - - keyUpHandler = (e: KeyboardEvent): void => { - if (e.code === "Escape") { - this.resetForms(); - this.open = false; - } - }; - - disconnectedCallback(): void { - super.disconnectedCallback(); - window.removeEventListener("keyup", this.keyUpHandler); + closeModal() { + this.resetForms(); + this.open = false; } resetForms(): void { @@ -93,6 +84,7 @@ export class ModalButton extends AKElement { onClick(): void { this.open = true; + this.dispatchEvent(new ModalShowEvent(this)); this.querySelectorAll("*").forEach((child) => { if ("requestUpdate" in child) { (child as AKElement).requestUpdate(); @@ -119,8 +111,7 @@ export class ModalButton extends AKElement { >
                    +
                    `; } } diff --git a/web/src/elements/cards/AggregatePromiseCard.ts b/web/src/elements/cards/AggregatePromiseCard.ts index 481edb2592..f3ea049210 100644 --- a/web/src/elements/cards/AggregatePromiseCard.ts +++ b/web/src/elements/cards/AggregatePromiseCard.ts @@ -1,4 +1,4 @@ -import { PFSize } from "@goauthentik/elements/Spinner"; +import { PFSize } from "@goauthentik/common/enums.js"; import "@goauthentik/elements/Spinner"; import { AggregateCard } from "@goauthentik/elements/cards/AggregateCard"; diff --git a/web/src/elements/charts/Chart.ts b/web/src/elements/charts/Chart.ts index 0501db22a2..4a12b376c0 100644 --- a/web/src/elements/charts/Chart.ts +++ b/web/src/elements/charts/Chart.ts @@ -1,4 +1,5 @@ import { EVENT_REFRESH, EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; +import { getRelativeTime } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/EmptyState"; import { @@ -18,7 +19,7 @@ import { ArcElement, BarElement } from "chart.js"; import { LinearScale, TimeScale } from "chart.js"; import "chartjs-adapter-moment"; -import { msg, str } from "@lit/localize"; +import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; import { property, state } from "lit/decorators.js"; @@ -161,14 +162,13 @@ export abstract class AKChart extends AKElement { timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string { const valueStamp = ticks[index]; - const delta = Date.now() - valueStamp.value; - const ago = Math.round(delta / 1000 / 3600); - return msg(str`${ago} hour(s) ago`); + return getRelativeTime(new Date(valueStamp.value)); } getOptions(): ChartOptions { return { maintainAspectRatio: false, + responsive: true, scales: { x: { type: "time", diff --git a/web/src/elements/controllers/ModalOrchestrationController.ts b/web/src/elements/controllers/ModalOrchestrationController.ts new file mode 100644 index 0000000000..2892caaebf --- /dev/null +++ b/web/src/elements/controllers/ModalOrchestrationController.ts @@ -0,0 +1,125 @@ +import { bound } from "@goauthentik/elements/decorators/bound.js"; + +import { LitElement, ReactiveController, ReactiveControllerHost } from "lit"; + +type ReactiveElementHost = Partial & LitElement; + +type ModalElement = LitElement & { closeModal(): void | boolean }; + +export class ModalShowEvent extends Event { + modal: ModalElement; + constructor(modal: ModalElement) { + super("ak-modal-show", { bubbles: true, composed: true }); + this.modal = modal; + } +} + +export class ModalHideEvent extends Event { + modal: ModalElement; + constructor(modal: ModalElement) { + super("ak-modal-hide", { bubbles: true, composed: true }); + this.modal = modal; + } +} + +declare global { + interface GlobalEventHandlersEventMap { + "ak-modal-show": ModalShowEvent; + "ak-modal-hide": ModalHideEvent; + } +} + +const modalIsLive = (modal: ModalElement) => modal.isConnected && modal.checkVisibility(); + +/** + * class ModalOrchetrationController + * + * A top-level controller that listens for requests from modals to be added to + * the management list, such that the *topmost* modal will be closed (and all + * references to it eliminated) whenever the user presses the Escape key. + * Can also take ModalHideEvent requests and automatically close the modal + * sending the event. + * + * Both events that this responds to expect a reference to the modal to be part + * of the event payload. + * + * If the `.closeModal()` method on the target modal returns `false` + * *explicitly*, it will abort cleanup and the stack will keep the record that + * the modal is still open. This allows `.closeModal()` to return `undefined` + * and still behave correctly. + */ + +export class ModalOrchestrationController implements ReactiveController { + host!: ReactiveElementHost; + + knownModals: ModalElement[] = []; + + constructor(host: ReactiveElementHost) { + this.host = host; + host.addController(this); + } + + hostConnected() { + window.addEventListener("keyup", this.handleKeyup); + window.addEventListener("ak-modal-show", this.addModal); + window.addEventListener("ak-modal-hide", this.closeModal); + } + + hostDisconnected() { + window.removeEventListener("keyup", this.handleKeyup); + window.removeEventListener("ak-modal-show", this.addModal); + window.removeEventListener("ak-modal-hide", this.closeModal); + } + + @bound + addModal(e: ModalShowEvent) { + this.knownModals = [...this.knownModals, e.modal]; + } + + scheduleCleanup(modal: ModalElement) { + setTimeout(() => { + this.knownModals = this.knownModals.filter((m) => modalIsLive(m) && modal !== m); + }, 0); + } + + @bound + closeModal(e: ModalHideEvent) { + const modal = e.modal; + if (!modalIsLive(modal)) { + return; + } + if (modal.closeModal() !== false) { + this.scheduleCleanup(modal); + } + } + + removeTopmostModal() { + const knownModals = [...this.knownModals]; + // Pop off modals until you find the first live one, schedule it to be closed, and make that + // cleaned list the current state. Since this is our *only* state object, this has the + // effect of creating a new "knownModals" collection with some semantics. + // eslint-disable-next-line no-constant-condition + while (true) { + const modal = knownModals.pop(); + if (!modal) { + break; + } + if (!modalIsLive(modal)) { + continue; + } + if (modal.closeModal() !== false) { + this.scheduleCleanup(modal); + } + break; + } + this.knownModals = knownModals; + } + + @bound + handleKeyup(e: KeyboardEvent) { + // The latter handles Firefox 37 and earlier. + if (e.key === "Escape" || e.key === "Esc") { + this.removeTopmostModal(); + } + } +} diff --git a/web/src/elements/decorators/bound.ts b/web/src/elements/decorators/bound.ts new file mode 100644 index 0000000000..52adbee68b --- /dev/null +++ b/web/src/elements/decorators/bound.ts @@ -0,0 +1,31 @@ +// Automatically binds a method to the `this` instance during instantiation. +// Uses the Typescript Experimental Decorator syntax, so we may be living with +// that for a long time. + +// MDN is *not* very helpful. The type for a PropertyDescriptor is kept in +// typescript/lib/lib.es5.d.ts, but the description of what everything in +// a descriptor does isn't specified in MDN in its own page, only in +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty + +// This decorator feels awkward. It gets a new instance of the method every time +// you reference the field. I wonder if there would be a way to create a lookup +// table; once you'd bound the method you could reuse that bound method for that +// instance, instead of throwing it away? + +export function bound( + target: unknown, + key: string, + descriptor: PropertyDescriptor, +): PropertyDescriptor { + if (typeof descriptor?.value !== "function") { + throw new Error("Only methods can be @bound."); + } + return { + configurable: true, + get() { + const method = descriptor.value.bind(this); + Object.defineProperty(this, key, { value: method, configurable: true, writable: true }); + return method; + }, + }; +} diff --git a/web/src/elements/enterprise/EnterpriseStatusBanner.ts b/web/src/elements/enterprise/EnterpriseStatusBanner.ts index 0ac1154572..b3360fb59a 100644 --- a/web/src/elements/enterprise/EnterpriseStatusBanner.ts +++ b/web/src/elements/enterprise/EnterpriseStatusBanner.ts @@ -1,19 +1,14 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKElement } from "@goauthentik/elements/Base"; +import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { customElement, property } from "lit/decorators.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; -import { EnterpriseApi, LicenseSummary } from "@goauthentik/api"; - @customElement("ak-enterprise-status") -export class EnterpriseStatusBanner extends AKElement { - @state() - summary?: LicenseSummary; - +export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) { @property() interface: "admin" | "user" | "" = ""; @@ -21,14 +16,10 @@ export class EnterpriseStatusBanner extends AKElement { return [PFBanner]; } - firstUpdated(): void { - new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((b) => { - this.summary = b; - }); - } - renderBanner(): TemplateResult { - return html`
                    + return html`
                    ${msg("Warning: The current user count has exceeded the configured licenses.")} ${msg("Click here for more info.")}
                    `; @@ -37,12 +28,12 @@ export class EnterpriseStatusBanner extends AKElement { render(): TemplateResult { switch (this.interface.toLowerCase()) { case "admin": - if (this.summary?.showAdminWarning || this.summary?.readOnly) { + if (this.licenseSummary?.showAdminWarning || this.licenseSummary?.readOnly) { return this.renderBanner(); } break; case "user": - if (this.summary?.showUserWarning || this.summary?.readOnly) { + if (this.licenseSummary?.showUserWarning || this.licenseSummary?.readOnly) { return this.renderBanner(); } break; diff --git a/web/src/elements/events/LogViewer.ts b/web/src/elements/events/LogViewer.ts new file mode 100644 index 0000000000..c49eb559c8 --- /dev/null +++ b/web/src/elements/events/LogViewer.ts @@ -0,0 +1,114 @@ +import { getRelativeTime } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-status-label"; +import "@goauthentik/elements/EmptyState"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; + +import { LogEvent, LogLevelEnum } from "@goauthentik/api"; + +@customElement("ak-log-viewer") +export class LogViewer extends Table { + @property({ attribute: false }) + logs?: LogEvent[] = []; + + expandable = true; + paginated = false; + + static get styles(): CSSResult[] { + return super.styles.concat(PFDescriptionList); + } + + async apiEndpoint(_page: number): Promise> { + return { + pagination: { + next: 0, + previous: 0, + count: this.logs?.length || 0, + current: 1, + totalPages: 1, + startIndex: 1, + endIndex: this.logs?.length || 0, + }, + results: this.logs || [], + }; + } + + renderEmpty(): TemplateResult { + return super.renderEmpty( + html` `, + ); + } + + renderExpanded(item: LogEvent): TemplateResult { + return html` +
                    +
                    +
                    +
                    + ${msg("Timestamp")} +
                    +
                    +
                    + ${item.timestamp.toLocaleString()} +
                    +
                    +
                    +
                    +
                    + ${msg("Attributes")} +
                    +
                    +
                    +
                    ${JSON.stringify(item.attributes, null, 4)}
                    +
                    +
                    +
                    +
                    +
                    + `; + } + + renderToolbarContainer(): TemplateResult { + return html``; + } + + columns(): TableColumn[] { + return [ + new TableColumn(msg("Time")), + new TableColumn(msg("Level")), + new TableColumn(msg("Event")), + new TableColumn(msg("Logger")), + ]; + } + + statusForItem(item: LogEvent): string { + switch (item.logLevel) { + case LogLevelEnum.Critical: + case LogLevelEnum.Error: + case LogLevelEnum.Exception: + return "error"; + case LogLevelEnum.Warn: + case LogLevelEnum.Warning: + return "warning"; + default: + return "info"; + } + } + + row(item: LogEvent): TemplateResult[] { + return [ + html`${getRelativeTime(item.timestamp)}`, + html``, + html`${item.event}`, + html`${item.logger}`, + ]; + } +} diff --git a/web/src/elements/forms/DeleteBulkForm.ts b/web/src/elements/forms/DeleteBulkForm.ts index 693184b715..c76f2e9e24 100644 --- a/web/src/elements/forms/DeleteBulkForm.ts +++ b/web/src/elements/forms/DeleteBulkForm.ts @@ -1,6 +1,6 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { PFSize } from "@goauthentik/common/enums.js"; import { MessageLevel } from "@goauthentik/common/messages"; -import { PFSize } from "@goauthentik/elements/Spinner"; import { ModalButton } from "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; @@ -131,6 +131,15 @@ export class DeleteBulkForm extends ModalButton { @property() actionSubtext?: string; + @property() + buttonLabel = msg("Delete"); + + /** + * Action shown in messages, for example `deleted` or `removed` + */ + @property() + action = msg("deleted"); + @property({ attribute: false }) metadata: (item: T) => BulkDeleteMetadata = (item: T) => { const rec = item as Record; @@ -222,7 +231,7 @@ export class DeleteBulkForm extends ModalButton { }} class="pf-m-danger" > - ${msg("Delete")}   { diff --git a/web/src/elements/forms/Form.ts b/web/src/elements/forms/Form.ts index d465a24350..7f08433ea9 100644 --- a/web/src/elements/forms/Form.ts +++ b/web/src/elements/forms/Form.ts @@ -1,10 +1,10 @@ -import { PreventFormSubmit } from "@goauthentik/app/elements/forms/helpers"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { MessageLevel } from "@goauthentik/common/messages"; -import { camelToSnake, convertToSlug } from "@goauthentik/common/utils"; +import { camelToSnake, convertToSlug, dateToUTC } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; +import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -69,7 +69,6 @@ export function serializeForm( return; } - // TODO: Tighten up the typing so that we can handle both. if ("akControl" in element.dataset) { assignValue(element, (element as unknown as AkControlElement).json(), json); return; @@ -79,6 +78,12 @@ export function serializeForm( if (element.hidden || !inputElement) { return; } + + if ("akControl" in inputElement.dataset) { + assignValue(element, (inputElement as unknown as AkControlElement).json(), json); + return; + } + // Skip elements that are writeOnly where the user hasn't clicked on the value if (element.writeOnly && !element.writeOnlyActivated) { return; @@ -99,7 +104,7 @@ export function serializeForm( inputElement.tagName.toLowerCase() === "input" && inputElement.type === "datetime-local" ) { - assignValue(inputElement, new Date(inputElement.valueAsNumber), json); + assignValue(inputElement, dateToUTC(new Date(inputElement.valueAsNumber)), json); } else if ( inputElement.tagName.toLowerCase() === "input" && "type" in inputElement.dataset && @@ -107,7 +112,7 @@ export function serializeForm( ) { // Workaround for Firefox <93, since 92 and older don't support // datetime-local fields - assignValue(inputElement, new Date(inputElement.value), json); + assignValue(inputElement, dateToUTC(new Date(inputElement.value)), json); } else if ( inputElement.tagName.toLowerCase() === "input" && inputElement.type === "checkbox" diff --git a/web/src/elements/forms/HorizontalFormElement.ts b/web/src/elements/forms/HorizontalFormElement.ts index 9d00327daf..c5973d9f19 100644 --- a/web/src/elements/forms/HorizontalFormElement.ts +++ b/web/src/elements/forms/HorizontalFormElement.ts @@ -36,6 +36,22 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; * */ +const isAkControl = (el: unknown): boolean => + el instanceof HTMLElement && + "dataset" in el && + el.dataset instanceof DOMStringMap && + "akControl" in el.dataset; + +const nameables = new Set([ + "input", + "textarea", + "select", + "ak-codemirror", + "ak-chip-group", + "ak-search-select", + "ak-radio", +]); + @customElement("ak-form-element-horizontal") export class HorizontalFormElement extends AKElement { static get styles(): CSSResult[] { @@ -112,19 +128,18 @@ export class HorizontalFormElement extends AKElement { }); } this.querySelectorAll("*").forEach((input) => { - switch (input.tagName.toLowerCase()) { - case "input": - case "textarea": - case "select": - case "ak-codemirror": - case "ak-chip-group": - case "ak-search-select": - case "ak-radio": - input.setAttribute("name", this.name); - break; - default: - return; + if (isAkControl(input) && !input.getAttribute("name")) { + input.setAttribute("name", this.name); + // This is fine; writeOnly won't apply to anything built this way. + return; } + + if (nameables.has(input.tagName.toLowerCase())) { + input.setAttribute("name", this.name); + } else { + return; + } + if (this.writeOnly && !this.writeOnlyActivated) { const i = input as HTMLInputElement; i.setAttribute("hidden", "true"); diff --git a/web/src/elements/forms/ModalForm.ts b/web/src/elements/forms/ModalForm.ts index 1930b7d752..e107729abd 100644 --- a/web/src/elements/forms/ModalForm.ts +++ b/web/src/elements/forms/ModalForm.ts @@ -2,6 +2,7 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants"; import "@goauthentik/elements/LoadingOverlay"; import { ModalButton } from "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; +import { ModalHideEvent } from "@goauthentik/elements/controllers/ModalOrchestrationController.js"; import { Form } from "@goauthentik/elements/forms/Form"; import { msg } from "@lit/localize"; @@ -36,15 +37,15 @@ export class ModalForm extends ModalButton { if (this.closeAfterSuccessfulSubmit) { this.open = false; form?.resetForm(); + this.dispatchEvent( + new CustomEvent(EVENT_REFRESH, { + bubbles: true, + composed: true, + }), + ); } this.loading = false; this.locked = false; - this.dispatchEvent( - new CustomEvent(EVENT_REFRESH, { - bubbles: true, - composed: true, - }), - ); }) .catch((exc) => { this.loading = false; @@ -92,8 +93,7 @@ export class ModalForm extends ModalButton { : html``} { - this.resetForms(); - this.open = false; + this.dispatchEvent(new ModalHideEvent(this)); }} class="pf-m-secondary" > diff --git a/web/src/elements/forms/Radio.ts b/web/src/elements/forms/Radio.ts index 27fbdf0b91..7730b87f77 100644 --- a/web/src/elements/forms/Radio.ts +++ b/web/src/elements/forms/Radio.ts @@ -14,8 +14,9 @@ import { randomId } from "../utils/randomId"; export interface RadioOption { label: string; description?: TemplateResult; - default: boolean; + default?: boolean; value: T; + disabled?: boolean; } @customElement("ak-radio") @@ -77,6 +78,9 @@ export class Radio extends CustomEmitterElement(AKElement) { // This is a controlled input. Stop the native event from escaping or affecting the // value. We'll do that ourselves. ev.stopPropagation(); + if (option.disabled) { + return; + } this.value = option.value; this.dispatchCustomEvent("change", { value: option.value }); this.dispatchCustomEvent("input", { value: option.value }); @@ -93,6 +97,7 @@ export class Radio extends CustomEmitterElement(AKElement) { name="${this.name}" id=${elId} .checked=${option.value === this.value} + .disabled=${option.disabled} /> ${option.description diff --git a/web/src/elements/forms/SearchSelect.ts b/web/src/elements/forms/SearchSelect.ts deleted file mode 100644 index 6c8865ca1f..0000000000 --- a/web/src/elements/forms/SearchSelect.ts +++ /dev/null @@ -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 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; - - @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( - ".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) { - 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` -
                  • - -
                  • - `; - })}`; - }; - render( - html`
                    -
                      - ${this.blankable - ? html` -
                    • - -
                    • - ` - : html``} - ${shouldRenderGroups - ? html`${groupedItems.map(([group, items], idx) => { - return html` -
                      -

                      ${group}

                      -
                        - ${renderGroup(items, idx)} -
                      -
                      - `; - })}` - : html`${renderGroup(groupedItems[0][1], 0)}`} -
                    -
                    `, - 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`
                    -
                    -
                    - { - this.query = (ev.target as HTMLInputElement).value; - this.updateData(); - }} - @focus=${() => { - this.open = true; - this.renderMenu(); - }} - @blur=${(ev: FocusEvent) => { - // For Safari, we get the
                      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} - /> -
                    -
                    -
                    `; - } -} diff --git a/web/src/elements/forms/SearchSelect/ak-search-select.ts b/web/src/elements/forms/SearchSelect/ak-search-select.ts new file mode 100644 index 0000000000..5077f713c9 --- /dev/null +++ b/web/src/elements/forms/SearchSelect/ak-search-select.ts @@ -0,0 +1,403 @@ +import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { APIErrorTypes, parseAPIError } from "@goauthentik/common/errors"; +import { ascii_letters, digits, groupBy, randomString } from "@goauthentik/common/utils"; +import { AKElement } from "@goauthentik/elements/Base"; +import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers"; +import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet"; +import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; + +import { msg, str } from "@lit/localize"; +import { TemplateResult, html, render } from "lit"; +import { customElement, property, state } 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"; + +import { ResponseError } from "@goauthentik/api"; + +type Group = [string, T[]]; + +@customElement("ak-search-select") +export class SearchSelect 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; + + // 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 object, if currently empty. Used as the + // native 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; + + @state() + error?: APIErrorTypes; + + static get styles() { + return [PFBase, PFForm, PFFormControl, PFSelect]; + } + + constructor() { + super(); + if (!document.adoptedStyleSheets.includes(PFDropdown)) { + document.adoptedStyleSheets = [ + ...document.adoptedStyleSheets, + ensureCSSStyleSheet(PFDropdown), + ]; + } + this.dropdownContainer = document.createElement("div"); + this.observer = new IntersectionObserver(() => { + this.open = false; + this.shadowRoot + ?.querySelectorAll( + ".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.dispatchCustomEvent("ak-change", { value: this.selectedObject }); + } + }); + this.objects = objects; + this.isFetchingData = false; + }) + .catch((exc: ResponseError) => { + this.isFetchingData = false; + this.objects = undefined; + parseAPIError(exc).then((err) => { + this.error = err; + }); + }); + } + + 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(); + } + + renderMenuItemWithDescription(obj: T, desc: TemplateResult, index: number) { + return html` +
                  • + +
                  • + `; + } + + renderMenuItemWithoutDescription(obj: T, index: number) { + return html` +
                  • + +
                  • + `; + } + + renderEmptyMenuItem() { + return html`
                  • + +
                  • `; + } + + onMenuItemClick(obj: T | undefined) { + return () => { + this.selectedObject = obj; + this.dispatchCustomEvent("ak-change", { value: this.selectedObject }); + 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, idx: number) { + return html` +
                    +

                    ${group}

                    +
                      + ${this.renderMenuGroup(items, idx)} +
                    +
                    + `; + } + + get groupedItems(): [boolean, Group[]] { + 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`
                    +
                      + ${this.blankable ? this.renderEmptyMenuItem() : html``} + ${shouldRenderGroups + ? html`${groupedItems.map(this.renderWithMenuGroupTitle)}` + : html`${this.renderMenuGroup(groupedItems[0][1], 0)}`} +
                    +
                    `, + this.dropdownContainer, + { host: this }, + ); + } + + get renderedValue() { + if (this.error) { + return msg(str`Failed to fetch objects: ${this.error.detail}`); + } + if (!this.objects) { + return msg("Loading..."); + } + if (this.selectedObject) { + return this.renderElement(this.selectedObject); + } + if (this.blankable) { + return this.emptyOption; + } + return ""; + } + + 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
                      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`
                      +
                      +
                      + +
                      +
                      +
                      `; + } +} + +export default SearchSelect; diff --git a/web/src/elements/forms/SearchSelect/index.ts b/web/src/elements/forms/SearchSelect/index.ts new file mode 100644 index 0000000000..f887f8344c --- /dev/null +++ b/web/src/elements/forms/SearchSelect/index.ts @@ -0,0 +1,4 @@ +import { SearchSelect } from "./ak-search-select"; + +export { SearchSelect }; +export default SearchSelect; diff --git a/web/src/elements/forms/helpers.ts b/web/src/elements/forms/helpers.ts index 50a4c3002b..afc0432de4 100644 --- a/web/src/elements/forms/helpers.ts +++ b/web/src/elements/forms/helpers.ts @@ -1,4 +1,4 @@ -import { HorizontalFormElement } from "@goauthentik/app/elements/forms/HorizontalFormElement"; +import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement"; export class PreventFormSubmit { // Stub class which can be returned by form elements to prevent the form from submitting diff --git a/web/src/elements/notifications/APIDrawer.ts b/web/src/elements/notifications/APIDrawer.ts index 8f93bd8d2e..20b56f17df 100644 --- a/web/src/elements/notifications/APIDrawer.ts +++ b/web/src/elements/notifications/APIDrawer.ts @@ -25,8 +25,11 @@ export class APIDrawer extends AKElement { PFContent, PFDropdown, css` + :host { + --header-height: 114px; + } .pf-c-notification-drawer__header { - height: 114px; + height: var(--header-height); align-items: center; } .pf-c-notification-drawer__header-action, @@ -41,6 +44,9 @@ export class APIDrawer extends AKElement { .pf-c-notification-drawer__body { overflow-x: hidden; } + .pf-c-notification-drawer__list { + max-height: calc(100vh - var(--header-height)); + } `, ]; } diff --git a/web/src/elements/notifications/NotificationDrawer.ts b/web/src/elements/notifications/NotificationDrawer.ts index e15cf73072..77c5b549f2 100644 --- a/web/src/elements/notifications/NotificationDrawer.ts +++ b/web/src/elements/notifications/NotificationDrawer.ts @@ -3,9 +3,11 @@ import { EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_REFRESH } from "@goauthentik/co import { actionToLabel } from "@goauthentik/common/labels"; import { MessageLevel } from "@goauthentik/common/messages"; import { me } from "@goauthentik/common/users"; +import { getRelativeTime } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -132,7 +134,9 @@ export class NotificationDrawer extends AKElement {

                    ${item.body}

                    ${item.created?.toLocaleString()} + ${getRelativeTime(item.created!)} + `; } diff --git a/web/src/elements/oauth/UserAccessTokenList.ts b/web/src/elements/oauth/UserAccessTokenList.ts new file mode 100644 index 0000000000..a0c007234e --- /dev/null +++ b/web/src/elements/oauth/UserAccessTokenList.ts @@ -0,0 +1,108 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-status-label"; +import "@goauthentik/elements/chips/Chip"; +import "@goauthentik/elements/chips/ChipGroup"; +import "@goauthentik/elements/forms/DeleteBulkForm"; +import { PaginatedResponse } from "@goauthentik/elements/table/Table"; +import { Table, TableColumn } from "@goauthentik/elements/table/Table"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; + +import { ExpiringBaseGrantModel, Oauth2Api, TokenModel } from "@goauthentik/api"; + +@customElement("ak-user-oauth-access-token-list") +export class UserOAuthAccessTokenList extends Table { + expandable = true; + + @property({ type: Number }) + userId?: number; + + static get styles(): CSSResult[] { + return super.styles.concat(PFFlex); + } + + async apiEndpoint(page: number): Promise> { + return new Oauth2Api(DEFAULT_CONFIG).oauth2AccessTokensList({ + user: this.userId, + ordering: "expires", + page: page, + pageSize: (await uiConfig()).pagination.perPage, + }); + } + + checkbox = true; + order = "-expires"; + + columns(): TableColumn[] { + return [ + new TableColumn(msg("Provider"), "provider"), + new TableColumn(msg("Revoked?"), "revoked"), + new TableColumn(msg("Expires"), "expires"), + new TableColumn(msg("Scopes"), "scope"), + ]; + } + + renderExpanded(item: TokenModel): TemplateResult { + return html` +
                    +
                    +
                    +

                    ${msg("ID Token")}

                    +
                    ${item.idToken}
                    +
                    +
                    +
                    + + + `; + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensUsedByList({ + id: item.pk, + }); + }} + .delete=${(item: ExpiringBaseGrantModel) => { + return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensDestroy({ + id: item.pk, + }); + }} + > + + `; + } + + row(item: TokenModel): TemplateResult[] { + return [ + html` ${item.provider?.name} `, + html``, + html`${item.expires + ? html`
                    ${getRelativeTime(item.expires)}
                    + ${item.expires.toLocaleString()}` + : msg("-")}`, + html` + ${item.scope.map((scope) => { + return html`${scope}`; + })} + `, + ]; + } +} diff --git a/web/src/elements/oauth/UserRefreshList.ts b/web/src/elements/oauth/UserRefreshTokenList.ts similarity index 76% rename from web/src/elements/oauth/UserRefreshList.ts rename to web/src/elements/oauth/UserRefreshTokenList.ts index 2c5915f745..63f325c414 100644 --- a/web/src/elements/oauth/UserRefreshList.ts +++ b/web/src/elements/oauth/UserRefreshTokenList.ts @@ -1,6 +1,9 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; +import "@goauthentik/elements/chips/Chip"; +import "@goauthentik/elements/chips/ChipGroup"; import "@goauthentik/elements/forms/DeleteBulkForm"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { Table, TableColumn } from "@goauthentik/elements/table/Table"; @@ -13,8 +16,8 @@ import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; import { ExpiringBaseGrantModel, Oauth2Api, TokenModel } from "@goauthentik/api"; -@customElement("ak-user-oauth-refresh-list") -export class UserOAuthRefreshList extends Table { +@customElement("ak-user-oauth-refresh-token-list") +export class UserOAuthRefreshTokenList extends Table { expandable = true; @property({ type: Number }) @@ -34,6 +37,7 @@ export class UserOAuthRefreshList extends Table { } checkbox = true; + clearOnRefresh = true; order = "-expires"; columns(): TableColumn[] { @@ -85,9 +89,21 @@ export class UserOAuthRefreshList extends Table { row(item: TokenModel): TemplateResult[] { return [ html` ${item.provider?.name} `, - html``, - html`${item.expires?.toLocaleString()}`, - html`${item.scope.join(", ")}`, + html``, + html`${item.expires + ? html`
                    ${getRelativeTime(item.expires)}
                    + ${item.expires.toLocaleString()}` + : msg("-")}`, + html` + ${item.scope.map((scope) => { + return html`${scope}`; + })} + `, ]; } } diff --git a/web/src/elements/rbac/ObjectPermissionModal.ts b/web/src/elements/rbac/ObjectPermissionModal.ts index e50be6b829..72dda84d53 100644 --- a/web/src/elements/rbac/ObjectPermissionModal.ts +++ b/web/src/elements/rbac/ObjectPermissionModal.ts @@ -1,4 +1,4 @@ -import { AKElement } from "@goauthentik/app/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/forms/ModalForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import "@goauthentik/elements/rbac/ObjectPermissionsPage"; @@ -38,6 +38,7 @@ export class ObjectPermissionsPageForm extends ModelForm { .model=${this.model} .objectPk=${this.objectPk} slot="form" + .embedded=${true} > `; } diff --git a/web/src/elements/rbac/ObjectPermissionsPage.ts b/web/src/elements/rbac/ObjectPermissionsPage.ts index a9ebd4d7aa..39443e1a20 100644 --- a/web/src/elements/rbac/ObjectPermissionsPage.ts +++ b/web/src/elements/rbac/ObjectPermissionsPage.ts @@ -1,10 +1,14 @@ -import { AKElement } from "@goauthentik/app/elements/Base"; -import "@goauthentik/app/elements/rbac/RoleObjectPermissionTable"; -import "@goauthentik/app/elements/rbac/UserObjectPermissionTable"; +import "@goauthentik/admin/roles/RoleAssignedGlobalPermissionsTable"; +import "@goauthentik/admin/roles/RoleAssignedObjectPermissionTable"; +import "@goauthentik/admin/users/UserAssignedGlobalPermissionsTable"; +import "@goauthentik/admin/users/UserAssignedObjectPermissionsTable"; +import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/Tabs"; +import "@goauthentik/elements/rbac/RoleObjectPermissionTable"; +import "@goauthentik/elements/rbac/UserObjectPermissionTable"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; @@ -24,20 +28,26 @@ export class ObjectPermissionPage extends AKElement { objectPk?: string | number; @property({ type: Boolean }) - showBanner = true; + embedded = false; - static get styles(): CSSResult[] { + static get styles() { return [PFBase, PFGrid, PFPage, PFCard, PFBanner]; } - render(): TemplateResult { - return html`${this.showBanner + render() { + return html`${!this.embedded ? html`
                    ${msg("RBAC is in preview.")} ${msg("Send us feedback!")}
                    ` - : html``} - + : nothing} + + ${this.model === RbacPermissionsAssignedByUsersListModelEnum.CoreUser + ? this.renderCoreUser() + : nothing} + ${this.model === RbacPermissionsAssignedByUsersListModelEnum.RbacRole + ? this.renderRbacRole() + : nothing}
                    -
                    User Object Permissions
                    +
                    ${msg("User Object Permissions")}
                    +
                    + ${msg("Permissions set on users which affect this object.")} +
                    -
                    Role Object Permissions
                    +
                    ${msg("Role Object Permissions")}
                    +
                    + ${msg("Permissions set on roles which affect this object.")} +
                    `; } + + renderCoreUser() { + return html` +
                    +
                    +
                    +
                    ${msg("Assigned global permissions")}
                    +
                    + ${msg( + "Permissions assigned to this user which affect all object instances of a given type.", + )} +
                    +
                    + + +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    ${msg("Assigned object permissions")}
                    +
                    + ${msg( + "Permissions assigned to this user affecting specific object instances.", + )} +
                    +
                    + + +
                    +
                    +
                    +
                    + `; + } + + renderRbacRole() { + return html` +
                    +
                    +
                    +
                    ${msg("Assigned global permissions")}
                    +
                    + ${msg( + "Permissions assigned to this role which affect all object instances of a given type.", + )} +
                    +
                    + + +
                    +
                    +
                    +
                    +
                    +
                    +
                    +
                    ${msg("Assigned object permissions")}
                    +
                    + ${msg( + "Permissions assigned to this user affecting specific object instances.", + )} +
                    +
                    + + +
                    +
                    +
                    +
                    + `; + } } diff --git a/web/src/elements/rbac/PermissionSelectModal.ts b/web/src/elements/rbac/PermissionSelectModal.ts index 648d88f170..e705f81a09 100644 --- a/web/src/elements/rbac/PermissionSelectModal.ts +++ b/web/src/elements/rbac/PermissionSelectModal.ts @@ -1,6 +1,6 @@ -import { groupBy } from "@goauthentik/app/common/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { groupBy } from "@goauthentik/common/utils"; import "@goauthentik/elements/buttons/SpinnerButton"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; @@ -67,7 +67,7 @@ export class PermissionSelectModal extends TableModal { renderModalInner(): TemplateResult { return html`
                    -

                    ${msg("Select permissions to grant")}

                    +

                    ${msg("Select permissions to assign")}

                    ${this.renderTable()}
                    diff --git a/web/src/elements/rbac/RoleObjectPermissionTable.ts b/web/src/elements/rbac/RoleObjectPermissionTable.ts index ee3c2161b4..6f335ae8b0 100644 --- a/web/src/elements/rbac/RoleObjectPermissionTable.ts +++ b/web/src/elements/rbac/RoleObjectPermissionTable.ts @@ -1,8 +1,8 @@ -import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; -import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; import "@goauthentik/elements/rbac/RoleObjectPermissionForm"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; @@ -29,6 +29,7 @@ export class RoleAssignedObjectPermissionTable extends Table> { const perms = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesList({ @@ -109,20 +110,13 @@ export class RoleAssignedObjectPermissionTable extends Table { const granted = item.permissions.filter((uperm) => uperm.codename === perm.codename).length > 0; - baseRow.push(html` - { - console.log(granted); - }} - class="pf-m-link" - > - ${granted - ? html`✓` - : html`X`} - - `); + baseRow.push( + html`${granted + ? html`` + : html``} `, + ); }); return baseRow; } diff --git a/web/src/elements/rbac/UserObjectPermissionForm.ts b/web/src/elements/rbac/UserObjectPermissionForm.ts index 3b3b664024..98a99de9c3 100644 --- a/web/src/elements/rbac/UserObjectPermissionForm.ts +++ b/web/src/elements/rbac/UserObjectPermissionForm.ts @@ -93,19 +93,24 @@ export class UserObjectPermissionForm extends ModelForm > - ${this.modelPermissions?.results.map((perm) => { - return html` - - `; - })} + ${perm.name} + + `; + })} `; } } diff --git a/web/src/elements/rbac/UserObjectPermissionTable.ts b/web/src/elements/rbac/UserObjectPermissionTable.ts index a746447cc8..34ff6d9f7e 100644 --- a/web/src/elements/rbac/UserObjectPermissionTable.ts +++ b/web/src/elements/rbac/UserObjectPermissionTable.ts @@ -1,8 +1,8 @@ -import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; -import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; import "@goauthentik/elements/rbac/UserObjectPermissionForm"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; @@ -29,6 +29,7 @@ export class UserAssignedObjectPermissionTable extends Table> { const perms = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersList({ @@ -44,7 +45,7 @@ export class UserAssignedObjectPermissionTable extends Table { - return !value.codename.startsWith("add_"); + return value.codename !== `add_${this.model?.split(".")[1]}`; }); this.modelPermissions = modelPermissions; return perms; @@ -112,13 +113,15 @@ export class UserAssignedObjectPermissionTable extends Table ${item.username} `]; this.modelPermissions?.results.forEach((perm) => { - let cell = html`X`; + let cell = html``; if (item.permissions.filter((uperm) => uperm.codename === perm.codename).length > 0) { cell = html`✓`; + >`; } else if (item.isSuperuser) { - cell = html`✓`; + cell = html``; } baseRow.push(cell); }); diff --git a/web/src/elements/router/RouterOutlet.ts b/web/src/elements/router/RouterOutlet.ts index 8aa54e0137..9d5687085c 100644 --- a/web/src/elements/router/RouterOutlet.ts +++ b/web/src/elements/router/RouterOutlet.ts @@ -91,7 +91,7 @@ export class RouterOutlet extends AKElement { let matchedRoute: RouteMatch | null = null; this.routes.some((route) => { const match = route.url.exec(activeUrl); - if (match != null) { + if (match !== null) { matchedRoute = new RouteMatch(route); matchedRoute.arguments = match.groups || {}; matchedRoute.fullUrl = activeUrl; diff --git a/web/src/elements/sidebar/SidebarBrand.ts b/web/src/elements/sidebar/SidebarBrand.ts index fa442b36c4..0d818b568f 100644 --- a/web/src/elements/sidebar/SidebarBrand.ts +++ b/web/src/elements/sidebar/SidebarBrand.ts @@ -1,6 +1,6 @@ import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants"; -import { first } from "@goauthentik/common/utils"; -import { AKElement, rootInterface } from "@goauthentik/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; +import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement } from "lit/decorators.js"; @@ -10,13 +10,13 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGlobal from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { CurrentTenant, UiThemeEnum } from "@goauthentik/api"; +import { CurrentBrand, UiThemeEnum } from "@goauthentik/api"; // If the viewport is wider than MIN_WIDTH, the sidebar // is shown besides the content, and not overlaid. export const MIN_WIDTH = 1200; -export const DefaultTenant: CurrentTenant = { +export const DefaultBrand: CurrentBrand = { brandingLogo: "/static/dist/assets/icons/icon_left_brand.svg", brandingFavicon: "/static/dist/assets/icons/icon.png", brandingTitle: "authentik", @@ -27,7 +27,7 @@ export const DefaultTenant: CurrentTenant = { }; @customElement("ak-sidebar-brand") -export class SidebarBrand extends AKElement { +export class SidebarBrand extends WithBrandConfig(AKElement) { static get styles(): CSSResult[] { return [ PFBase, @@ -43,7 +43,6 @@ export class SidebarBrand extends AKElement { min-height: 114px; } .pf-c-brand img { - width: 100%; padding: 0 0.5rem; height: 42px; } @@ -85,10 +84,7 @@ export class SidebarBrand extends AKElement {
                    authentik Logo diff --git a/web/src/elements/sidebar/SidebarItem.ts b/web/src/elements/sidebar/SidebarItem.ts index 26cdb975e4..1d38671a44 100644 --- a/web/src/elements/sidebar/SidebarItem.ts +++ b/web/src/elements/sidebar/SidebarItem.ts @@ -208,7 +208,7 @@ export class SidebarItem extends AKElement { } renderWithLabel() { - html` + return html` diff --git a/web/src/elements/table/Table.ts b/web/src/elements/table/Table.ts index 0b78c4dfb1..588dc01df4 100644 --- a/web/src/elements/table/Table.ts +++ b/web/src/elements/table/Table.ts @@ -1,5 +1,5 @@ -import { APIErrorTypes, parseAPIError } from "@goauthentik/app/common/errors"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { APIErrorTypes, parseAPIError } from "@goauthentik/common/errors"; import { groupBy } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/EmptyState"; @@ -27,6 +27,11 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { Pagination, ResponseError } from "@goauthentik/api"; +export interface TableLike { + order?: string; + fetch: () => void; +} + export class TableColumn { title: string; orderBy?: string; @@ -38,7 +43,7 @@ export class TableColumn { this.orderBy = orderBy; } - headerClickHandler(table: Table): void { + headerClickHandler(table: TableLike): void { if (!this.orderBy) { return; } @@ -46,7 +51,7 @@ export class TableColumn { table.fetch(); } - private getSortIndicator(table: Table): string { + private getSortIndicator(table: TableLike): string { switch (table.order) { case this.orderBy: return "fa-long-arrow-alt-down"; @@ -57,7 +62,7 @@ export class TableColumn { } } - renderSortable(table: Table): TemplateResult { + renderSortable(table: TableLike): TemplateResult { return html` `; } - render(table: Table): TemplateResult { + render(table: TableLike): TemplateResult { const classes = { "pf-c-table__sort": !!this.orderBy, "pf-m-selected": table.order === this.orderBy || table.order === `-${this.orderBy}`, @@ -89,7 +94,7 @@ export interface PaginatedResponse { results: Array; } -export abstract class Table extends AKElement { +export abstract class Table extends AKElement implements TableLike { abstract apiEndpoint(page: number): Promise>; abstract columns(): TableColumn[]; abstract row(item: T): TemplateResult[]; @@ -114,15 +119,29 @@ export abstract class Table extends AKElement { @property({ type: Number }) page = getURLParam("tablePage", 1); + /** @prop + * + * Set if your `selectedElements` use of the selection box is to enable bulk-delete, so that + * stale data is cleared out when the API returns a new list minus the deleted entries. + */ + @property({ attribute: "clear-on-refresh", type: Boolean, reflect: true }) + clearOnRefresh = false; + @property({ type: String }) order?: string; @property({ type: String }) - search: string = getURLParam("search", ""); + search: string = ""; @property({ type: Boolean }) checkbox = false; + @property({ type: Boolean }) + clickable = false; + + @property({ attribute: false }) + clickHandler: (item: T) => void = () => {}; + @property({ type: Boolean }) radioSelect = false; @@ -161,15 +180,27 @@ export abstract class Table extends AKElement { .pf-c-table tbody .pf-c-table__check input { margin-top: calc(var(--pf-c-table__check--input--MarginTop) + 1px); } + .pf-c-toolbar__content { + row-gap: var(--pf-global--spacer--sm); + } + .pf-c-toolbar__item .pf-c-input-group { + padding: 0 var(--pf-global--spacer--sm); + } `, ]; } constructor() { super(); - this.addEventListener(EVENT_REFRESH, () => { - this.fetch(); + this.addEventListener(EVENT_REFRESH, async () => { + await this.fetch(); + if (this.clearOnRefresh) { + this.selectedElements = []; + } }); + if (this.searchEnabled()) { + this.search = getURLParam("search", ""); + } } public groupBy(items: T[]): [string, T[]][] { @@ -356,8 +387,12 @@ export abstract class Table extends AKElement { return html` { + this.clickHandler(item); + } + : itemSelectHandler} > ${this.checkbox ? renderCheckbox() : html``} ${this.expandable ? renderExpansion() : html``} diff --git a/web/src/elements/table/TableModal.ts b/web/src/elements/table/TableModal.ts index 328f5ffdff..50311d8aef 100644 --- a/web/src/elements/table/TableModal.ts +++ b/web/src/elements/table/TableModal.ts @@ -1,6 +1,7 @@ +import { PFSize } from "@goauthentik/common/enums.js"; import { AKElement } from "@goauthentik/elements/Base"; -import { PFSize } from "@goauthentik/elements/Spinner"; import { MODAL_BUTTON_STYLES } from "@goauthentik/elements/buttons/ModalButton"; +import { ModalShowEvent } from "@goauthentik/elements/controllers/ModalOrchestrationController.js"; import { Table } from "@goauthentik/elements/table/Table"; import { CSSResult } from "lit"; @@ -19,7 +20,18 @@ export abstract class TableModal extends Table { size: PFSize = PFSize.Large; @property({ type: Boolean }) - open = false; + set open(value: boolean) { + this._open = value; + if (value) { + this.fetch(); + } + } + + get open(): boolean { + return this._open; + } + + _open = false; static get styles(): CSSResult[] { return super.styles.concat( @@ -33,14 +45,16 @@ export abstract class TableModal extends Table { ); } - constructor() { - super(); - window.addEventListener("keyup", (e) => { - if (e.code === "Escape") { - this.resetForms(); - this.open = false; - } - }); + public async fetch(): Promise { + if (!this.open) { + return; + } + return super.fetch(); + } + + closeModal() { + this.resetForms(); + this.open = false; } resetForms(): void { @@ -53,6 +67,7 @@ export abstract class TableModal extends Table { onClick(): void { this.open = true; + this.dispatchEvent(new ModalShowEvent(this)); this.querySelectorAll("*").forEach((child) => { if ("requestUpdate" in child) { (child as AKElement).requestUpdate(); diff --git a/web/src/elements/types.ts b/web/src/elements/types.ts new file mode 100644 index 0000000000..c0247b1e91 --- /dev/null +++ b/web/src/elements/types.ts @@ -0,0 +1,11 @@ +import { AKElement } from "@goauthentik/elements/Base"; + +import { ReactiveControllerHost } from "lit"; + +export type ReactiveElementHost = Partial & T; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type Constructor = new (...args: any[]) => T; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AbstractConstructor = abstract new (...args: any[]) => T; diff --git a/web/src/elements/user/SessionList.ts b/web/src/elements/user/SessionList.ts index b0861763d0..01c4d900c0 100644 --- a/web/src/elements/user/SessionList.ts +++ b/web/src/elements/user/SessionList.ts @@ -1,8 +1,10 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/elements/forms/DeleteBulkForm"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { Table, TableColumn } from "@goauthentik/elements/table/Table"; +import getUnicodeFlagIcon from "country-flag-icons/unicode"; import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; @@ -25,11 +27,13 @@ export class AuthenticatedSessionList extends Table { } checkbox = true; + clearOnRefresh = true; order = "-expires"; columns(): TableColumn[] { return [ new TableColumn(msg("Last IP"), "last_ip"), + new TableColumn(msg("Last used"), "last_used"), new TableColumn(msg("Expires"), "expires"), ]; } @@ -65,10 +69,17 @@ export class AuthenticatedSessionList extends Table { row(item: AuthenticatedSession): TemplateResult[] { return [ html`
                    - ${item.current ? html`${msg("(Current session)")} ` : html``}${item.lastIp} + ${item.geoIp?.country + ? html`${getUnicodeFlagIcon(item.geoIp.country)} ` + : html``} + ${item.current ? html`${msg("(Current session)")} ` : html``} + ${item.lastIp}
                    ${item.userAgent.userAgent?.family}, ${item.userAgent.os?.family}`, - html`${item.expires?.toLocaleString()}`, + html`
                    ${getRelativeTime(item.lastUsed)}
                    + ${item.lastUsed?.toLocaleString()}`, + html`
                    ${getRelativeTime(item.expires || new Date())}
                    + ${item.expires?.toLocaleString()}`, ]; } } diff --git a/web/src/elements/user/UserConsentList.ts b/web/src/elements/user/UserConsentList.ts index 56a59ebe10..3d992faad7 100644 --- a/web/src/elements/user/UserConsentList.ts +++ b/web/src/elements/user/UserConsentList.ts @@ -1,5 +1,8 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; +import { getRelativeTime } from "@goauthentik/common/utils"; +import "@goauthentik/elements/chips/Chip"; +import "@goauthentik/elements/chips/ChipGroup"; import "@goauthentik/elements/forms/DeleteBulkForm"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { Table, TableColumn } from "@goauthentik/elements/table/Table"; @@ -25,6 +28,7 @@ export class UserConsentList extends Table { } checkbox = true; + clearOnRefresh = true; order = "-expires"; columns(): TableColumn[] { @@ -60,8 +64,17 @@ export class UserConsentList extends Table { row(item: UserConsent): TemplateResult[] { return [ html`${item.application.name}`, - html`${item.expires?.toLocaleString()}`, - html`${item.permissions || "-"}`, + html`${item.expires && item.expiring + ? html`
                    ${getRelativeTime(item.expires)}
                    + ${item.expires.toLocaleString()}` + : msg("-")}`, + html`${item.permissions + ? html` + ${item.permissions.split(" ").map((perm) => { + return html`${perm}`; + })} + ` + : html`-`}`, ]; } } diff --git a/web/src/user/user-settings/BaseUserSettings.ts b/web/src/elements/user/sources/BaseUserSettings.ts similarity index 100% rename from web/src/user/user-settings/BaseUserSettings.ts rename to web/src/elements/user/sources/BaseUserSettings.ts diff --git a/web/src/user/user-settings/sources/SourceSettings.ts b/web/src/elements/user/sources/SourceSettings.ts similarity index 68% rename from web/src/user/user-settings/sources/SourceSettings.ts rename to web/src/elements/user/sources/SourceSettings.ts index 203eef9f3d..e1b26d3247 100644 --- a/web/src/user/user-settings/sources/SourceSettings.ts +++ b/web/src/elements/user/sources/SourceSettings.ts @@ -1,18 +1,16 @@ -import { renderSourceIcon } from "@goauthentik/app/admin/sources/utils"; +import { renderSourceIcon } from "@goauthentik/admin/sources/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; -import { me } from "@goauthentik/common/users"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/EmptyState"; -import "@goauthentik/user/user-settings/sources/SourceSettingsOAuth"; -import "@goauthentik/user/user-settings/sources/SourceSettingsPlex"; -import "@goauthentik/user/user-settings/sources/SourceSettingsSAML"; +import "@goauthentik/elements/user/sources/SourceSettingsOAuth"; +import "@goauthentik/elements/user/sources/SourceSettingsPlex"; +import "@goauthentik/elements/user/sources/SourceSettingsSAML"; import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css"; import { PaginatedUserSourceConnectionList, SourcesApi, UserSetting } from "@goauthentik/api"; @@ -25,10 +23,15 @@ export class UserSourceSettingsPage extends AKElement { @property({ attribute: false }) connections?: PaginatedUserSourceConnectionList; + @property({ type: Number }) + userId?: number; + + @property({ type: Boolean }) + canConnect = true; + static get styles(): CSSResult[] { return [ PFDataList, - PFContent, css` .pf-c-data-list__cell { display: flex; @@ -57,10 +60,9 @@ export class UserSourceSettingsPage extends AKElement { } async firstUpdated(): Promise { - const user = await me(); this.sourceSettings = await new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList(); this.connections = await new SourcesApi(DEFAULT_CONFIG).sourcesUserConnectionsAllList({ - user: user.user.pk, + user: this.userId, }); } @@ -79,29 +81,26 @@ export class UserSourceSettingsPage extends AKElement { switch (source.component) { case "ak-user-settings-source-oauth": return html` `; case "ak-user-settings-source-plex": return html` `; case "ak-user-settings-source-saml": return html` `; default: @@ -112,23 +111,17 @@ export class UserSourceSettingsPage extends AKElement { } render(): TemplateResult { - return html`
                    -

                    - ${msg( - "Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials.", - )} -

                    -
                    -
                      - ${this.sourceSettings - ? html` - ${this.sourceSettings.length < 1 - ? html`` - : html` - ${this.sourceSettings.map((source) => { - return html`
                    • + return html`
                        + ${this.sourceSettings + ? html` + ${this.sourceSettings.length < 1 + ? html`` + : html` + ${this.sourceSettings.map((source) => { + return html`
                      • +
                        ${renderSourceIcon( @@ -141,12 +134,13 @@ export class UserSourceSettingsPage extends AKElement { ${this.renderSourceSettings(source)}
                        -
                      • `; - })} - `} - ` - : html` - `} -
                      `; +
                    + `; + })} + `} + ` + : html` + `} + `; } } diff --git a/web/src/user/user-settings/sources/SourceSettingsOAuth.ts b/web/src/elements/user/sources/SourceSettingsOAuth.ts similarity index 82% rename from web/src/user/user-settings/sources/SourceSettingsOAuth.ts rename to web/src/elements/user/sources/SourceSettingsOAuth.ts index 2461b487f9..39eda5daca 100644 --- a/web/src/user/user-settings/sources/SourceSettingsOAuth.ts +++ b/web/src/elements/user/sources/SourceSettingsOAuth.ts @@ -3,12 +3,11 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { MessageLevel } from "@goauthentik/common/messages"; import "@goauthentik/elements/Spinner"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; -import { BaseUserSettings } from "@goauthentik/user/user-settings/BaseUserSettings"; +import { BaseUserSettings } from "@goauthentik/elements/user/sources/BaseUserSettings"; import { msg, str } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { ifDefined } from "lit/directives/if-defined.js"; import { SourcesApi } from "@goauthentik/api"; @@ -57,13 +56,16 @@ export class SourceSettingsOAuth extends BaseUserSettings { ${msg("Disconnect")} `; } - return html`
                    - ${msg("Connect")} - `; + if (this.configureUrl) { + return html` + ${msg("Connect")} + `; + } + return html`${msg("-")}`; } } diff --git a/web/src/user/user-settings/sources/SourceSettingsPlex.ts b/web/src/elements/user/sources/SourceSettingsPlex.ts similarity index 90% rename from web/src/user/user-settings/sources/SourceSettingsPlex.ts rename to web/src/elements/user/sources/SourceSettingsPlex.ts index a1c23cf57a..5d3370b70a 100644 --- a/web/src/user/user-settings/sources/SourceSettingsPlex.ts +++ b/web/src/elements/user/sources/SourceSettingsPlex.ts @@ -4,7 +4,7 @@ import { PlexAPIClient, popupCenterScreen } from "@goauthentik/common/helpers/pl import { MessageLevel } from "@goauthentik/common/messages"; import "@goauthentik/elements/Spinner"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; -import { BaseUserSettings } from "@goauthentik/user/user-settings/BaseUserSettings"; +import { BaseUserSettings } from "@goauthentik/elements/user/sources/BaseUserSettings"; import { msg, str } from "@lit/localize"; import { TemplateResult, html } from "lit"; @@ -77,8 +77,11 @@ export class SourceSettingsPlex extends BaseUserSettings { ${msg("Disconnect")} `; } - return html``; + if (this.configureUrl) { + return html``; + } + return html`${msg("-")}`; } } diff --git a/web/src/user/user-settings/sources/SourceSettingsSAML.ts b/web/src/elements/user/sources/SourceSettingsSAML.ts similarity index 82% rename from web/src/user/user-settings/sources/SourceSettingsSAML.ts rename to web/src/elements/user/sources/SourceSettingsSAML.ts index da88645c3e..df13a5eaaa 100644 --- a/web/src/user/user-settings/sources/SourceSettingsSAML.ts +++ b/web/src/elements/user/sources/SourceSettingsSAML.ts @@ -3,12 +3,11 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { MessageLevel } from "@goauthentik/common/messages"; import "@goauthentik/elements/Spinner"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; -import { BaseUserSettings } from "@goauthentik/user/user-settings/BaseUserSettings"; +import { BaseUserSettings } from "@goauthentik/elements/user/sources/BaseUserSettings"; import { msg, str } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { ifDefined } from "lit/directives/if-defined.js"; import { SourcesApi } from "@goauthentik/api"; @@ -57,13 +56,16 @@ export class SourceSettingsSAML extends BaseUserSettings { ${msg("Disconnect")} `; } - return html` - ${msg("Connect")} - `; + if (this.configureUrl) { + return html` + ${msg("Connect")} + `; + } + return html`${msg("-")}`; } } diff --git a/web/src/elements/utils/debounce.ts b/web/src/elements/utils/debounce.ts new file mode 100644 index 0000000000..ab9ac90a81 --- /dev/null +++ b/web/src/elements/utils/debounce.ts @@ -0,0 +1,13 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Callback = (...args: any[]) => any; +export function debounce(callback: F, wait: number) { + let timeout: ReturnType; + return (...args: Parameters) => { + // @ts-ignore + const context: T = this satisfies object; + if (timeout !== undefined) { + clearTimeout(timeout); + } + timeout = setTimeout(() => callback.apply(context, args), wait); + }; +} diff --git a/web/src/elements/utils/ensureCSSStyleSheet.ts b/web/src/elements/utils/ensureCSSStyleSheet.ts new file mode 100644 index 0000000000..d809ca69b6 --- /dev/null +++ b/web/src/elements/utils/ensureCSSStyleSheet.ts @@ -0,0 +1,35 @@ +import { CSSResult, unsafeCSS } from "lit"; + +const supportsAdoptingStyleSheets: boolean = + window.ShadowRoot && + (window.ShadyCSS === undefined || window.ShadyCSS.nativeShadow) && + "adoptedStyleSheets" in Document.prototype && + "replace" in CSSStyleSheet.prototype; + +function stringToStylesheet(css: string) { + if (supportsAdoptingStyleSheets) { + const sheet = unsafeCSS(css).styleSheet; + if (sheet === undefined) { + throw new Error( + `CSS processing error: undefined stylesheet from string. Source: ${css}`, + ); + } + return sheet; + } + + const sheet = new CSSStyleSheet(); + sheet.replaceSync(css); + return sheet; +} + +function cssResultToStylesheet(css: CSSResult) { + const sheet = css.styleSheet; + return sheet ? sheet : stringToStylesheet(css.toString()); +} + +export const ensureCSSStyleSheet = (css: string | CSSStyleSheet | CSSResult): CSSStyleSheet => + css instanceof CSSResult + ? cssResultToStylesheet(css) + : typeof css === "string" + ? stringToStylesheet(css) + : css; diff --git a/web/src/elements/utils/eventEmitter.ts b/web/src/elements/utils/eventEmitter.ts index 54b4728257..1a3555eb24 100644 --- a/web/src/elements/utils/eventEmitter.ts +++ b/web/src/elements/utils/eventEmitter.ts @@ -9,22 +9,26 @@ export const isCustomEvent = (v: any): v is CustomEvent => export function CustomEmitterElement>(superclass: T) { return class EmmiterElementHandler extends superclass { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - dispatchCustomEvent(eventName: string, detail: any = {}, options = {}) { + dispatchCustomEvent( + eventName: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + detail: any = {}, + options = {}, + ) { const fullDetail = typeof detail === "object" && !Array.isArray(detail) ? { - target: this, ...detail, } : detail; + this.dispatchEvent( new CustomEvent(eventName, { composed: true, bubbles: true, ...options, detail: fullDetail, - }), + }) as F, ); } }; diff --git a/web/src/elements/wizard/TypeCreateWizardPage.ts b/web/src/elements/wizard/TypeCreateWizardPage.ts new file mode 100644 index 0000000000..567edd579d --- /dev/null +++ b/web/src/elements/wizard/TypeCreateWizardPage.ts @@ -0,0 +1,146 @@ +import "@goauthentik/admin/common/ak-license-notice"; +import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; +import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; + +import { msg, str } from "@lit/localize"; +import { CSSResult, TemplateResult, css, html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFRadio from "@patternfly/patternfly/components/Radio/radio.css"; +import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { TypeCreate } from "@goauthentik/api"; + +export enum TypeCreateWizardPageLayouts { + list = "list", + grid = "grid", +} + +@customElement("ak-wizard-page-type-create") +export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) { + @property({ attribute: false }) + types: TypeCreate[] = []; + + @property({ attribute: false }) + selectedType?: TypeCreate; + + @property({ type: String }) + layout: TypeCreateWizardPageLayouts = TypeCreateWizardPageLayouts.list; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFForm, + PFGrid, + PFRadio, + PFCard, + css` + .pf-c-card__header-main img { + max-height: 2em; + min-height: 2em; + } + :host([theme="dark"]) .pf-c-card__header-main img { + filter: invert(1); + } + `, + ]; + } + + sidebarLabel = () => msg("Select type"); + + activeCallback: () => Promise = async () => { + this.host.isValid = false; + if (this.selectedType) { + this.selectDispatch(this.selectedType); + } + }; + + private selectDispatch(type: TypeCreate) { + this.dispatchEvent( + new CustomEvent("select", { + detail: type, + bubbles: true, + composed: true, + }), + ); + } + + renderGrid(): TemplateResult { + return html`
                    + ${this.types.map((type, idx) => { + const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense; + return html`
                    { + if (requiresEnterprise) { + return; + } + this.selectDispatch(type); + this.selectedType = type; + }} + > + ${type.iconUrl + ? html`
                    +
                    + ${msg(str`${type.name} +
                    +
                    ` + : nothing} +
                    ${type.name}
                    +
                    ${type.description}
                    + ${requiresEnterprise + ? html` ` + : nothing} +
                    `; + })} +
                    `; + } + + renderList(): TemplateResult { + return html`
                    + ${this.types.map((type) => { + const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense; + return html`
                    + { + this.selectDispatch(type); + }} + ?disabled=${requiresEnterprise} + /> + + ${type.description} + ${requiresEnterprise + ? html`` + : nothing} + +
                    `; + })} +
                    `; + } + + render(): TemplateResult { + switch (this.layout) { + case TypeCreateWizardPageLayouts.grid: + return this.renderGrid(); + case TypeCreateWizardPageLayouts.list: + return this.renderList(); + } + } +} diff --git a/web/src/elements/wizard/Wizard.ts b/web/src/elements/wizard/Wizard.ts index 95fc88a8ca..27f9e7f409 100644 --- a/web/src/elements/wizard/Wizard.ts +++ b/web/src/elements/wizard/Wizard.ts @@ -5,7 +5,7 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; import { msg } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { property } from "@lit/reactive-element/decorators/property.js"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, TemplateResult, css, html } from "lit"; import { state } from "lit/decorators.js"; import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css"; @@ -36,7 +36,14 @@ export class Wizard extends ModalButton { isValid = false; static get styles(): CSSResult[] { - return super.styles.concat(PFWizard); + return super.styles.concat( + PFWizard, + css` + .pf-c-modal-box { + height: 75%; + } + `, + ); } @state() diff --git a/web/src/enterprise/rac/index.ts b/web/src/enterprise/rac/index.ts new file mode 100644 index 0000000000..b2900957cc --- /dev/null +++ b/web/src/enterprise/rac/index.ts @@ -0,0 +1,330 @@ +import { TITLE_DEFAULT } from "@goauthentik/common/constants"; +import { Interface } from "@goauthentik/elements/Interface"; +import "@goauthentik/elements/LoadingOverlay"; +import Guacamole from "guacamole-common-js"; + +import { msg, str } from "@lit/localize"; +import { CSSResult, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +import AKGlobal from "@goauthentik/common/styles/authentik.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFPage from "@patternfly/patternfly/components/Page/page.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +enum GuacClientState { + IDLE = 0, + CONNECTING = 1, + WAITING = 2, + CONNECTED = 3, + DISCONNECTING = 4, + DISCONNECTED = 5, +} + +const AUDIO_INPUT_MIMETYPE = "audio/L16;rate=44100,channels=2"; +const RECONNECT_ATTEMPTS_INITIAL = 5; +const RECONNECT_ATTEMPTS = 5; + +@customElement("ak-rac") +export class RacInterface extends Interface { + static get styles(): CSSResult[] { + return [ + PFBase, + PFPage, + PFContent, + AKGlobal, + css` + :host { + cursor: none; + } + canvas { + z-index: unset !important; + } + .container { + overflow: hidden; + height: 100vh; + background-color: black; + display: flex; + justify-content: center; + align-items: center; + } + ak-loading-overlay { + z-index: 5; + } + `, + ]; + } + + client?: Guacamole.Client; + tunnel?: Guacamole.Tunnel; + + @state() + container?: HTMLElement; + + @state() + clientState?: GuacClientState; + + @state() + reconnectingMessage = ""; + + @property() + token?: string; + + @property() + endpointName?: string; + + @state() + clipboardWatcherTimer = 0; + + _previousClipboardValue: unknown; + + // Set to `true` if we've successfully connected once + hasConnected = false; + // Keep track of current connection attempt + connectionAttempt = 0; + + static domSize(): { width: number; height: number } { + const size = document.body.getBoundingClientRect(); + return { + width: size.width * window.devicePixelRatio, + height: size.height * window.devicePixelRatio, + }; + } + + constructor() { + super(); + this.initKeyboard(); + this.checkClipboard(); + this.clipboardWatcherTimer = setInterval( + this.checkClipboard.bind(this), + 500, + ) as unknown as number; + } + + connectedCallback(): void { + super.connectedCallback(); + window.addEventListener( + "focus", + () => { + this.checkClipboard(); + }, + { + capture: false, + }, + ); + window.addEventListener("resize", () => { + this.client?.sendSize( + Math.floor(RacInterface.domSize().width), + Math.floor(RacInterface.domSize().height), + ); + }); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + clearInterval(this.clipboardWatcherTimer); + } + + async firstUpdated(): Promise { + this.updateTitle(); + const wsUrl = `${window.location.protocol.replace("http", "ws")}//${ + window.location.host + }/ws/rac/${this.token}/`; + this.tunnel = new Guacamole.WebSocketTunnel(wsUrl); + this.tunnel.receiveTimeout = 10 * 1000; // 10 seconds + this.tunnel.onerror = (status) => { + console.debug("authentik/rac: tunnel error: ", status); + this.reconnect(); + }; + this.client = new Guacamole.Client(this.tunnel); + this.client.onerror = (err) => { + console.debug("authentik/rac: error: ", err); + this.reconnect(); + }; + this.client.onstatechange = (state) => { + this.clientState = state; + if (state === GuacClientState.CONNECTED) { + this.onConnected(); + } + }; + this.client.onclipboard = (stream, mimetype) => { + // If the received data is text, read it as a simple string + if (/^text\//.exec(mimetype)) { + const reader = new Guacamole.StringReader(stream); + let data = ""; + reader.ontext = (text) => { + data += text; + }; + reader.onend = () => { + this._previousClipboardValue = data; + navigator.clipboard.writeText(data); + }; + } else { + const reader = new Guacamole.BlobReader(stream, mimetype); + reader.onend = () => { + const blob = reader.getBlob(); + navigator.clipboard.write([ + new ClipboardItem({ + [blob.type]: blob, + }), + ]); + }; + } + console.debug("authentik/rac: updated clipboard from remote"); + }; + const params = new URLSearchParams(); + params.set("screen_width", Math.floor(RacInterface.domSize().width).toString()); + params.set("screen_height", Math.floor(RacInterface.domSize().height).toString()); + params.set("screen_dpi", (window.devicePixelRatio * 96).toString()); + this.client.connect(params.toString()); + } + + reconnect(): void { + this.clientState = undefined; + this.connectionAttempt += 1; + if (!this.hasConnected) { + // Check connection attempts if we haven't had a successful connection + if (this.connectionAttempt >= RECONNECT_ATTEMPTS_INITIAL) { + this.hasConnected = true; + this.reconnectingMessage = msg( + str`Connection failed after ${this.connectionAttempt} attempts.`, + ); + return; + } + } else { + if (this.connectionAttempt >= RECONNECT_ATTEMPTS) { + this.reconnectingMessage = msg( + str`Connection failed after ${this.connectionAttempt} attempts.`, + ); + return; + } + } + const delay = 500 * this.connectionAttempt; + this.reconnectingMessage = msg( + str`Re-connecting in ${Math.max(1, delay / 1000)} second(s).`, + ); + setTimeout(() => { + this.firstUpdated(); + }, delay); + } + + updateTitle(): void { + let title = this.brand?.brandingTitle || TITLE_DEFAULT; + if (this.endpointName) { + title = `${this.endpointName} - ${title}`; + } + document.title = `${title}`; + } + + onConnected(): void { + console.debug("authentik/rac: connected"); + if (!this.client) { + return; + } + this.hasConnected = true; + this.container = this.client.getDisplay().getElement(); + this.initMouse(this.container); + this.client?.sendSize( + Math.floor(RacInterface.domSize().width), + Math.floor(RacInterface.domSize().height), + ); + } + + initMouse(container: HTMLElement): void { + const mouse = new Guacamole.Mouse(container); + const handler = (mouseState: Guacamole.Mouse.State, scaleMouse = false) => { + if (!this.client) return; + + if (scaleMouse) { + mouseState.y = mouseState.y / this.client.getDisplay().getScale(); + mouseState.x = mouseState.x / this.client.getDisplay().getScale(); + } + + this.client.sendMouseState(mouseState); + }; + // @ts-ignore + mouse.onEach(["mouseup", "mousedown"], (ev: Guacamole.Mouse.Event) => { + this.container?.focus(); + handler(ev.state); + }); + // @ts-ignore + mouse.on("mousemove", (ev: Guacamole.Mouse.Event) => { + handler(ev.state, true); + }); + } + + initAudioInput(): void { + const stream = this.client?.createAudioStream(AUDIO_INPUT_MIMETYPE); + if (!stream) return; + // Guacamole.AudioPlayer + const recorder = Guacamole.AudioRecorder.getInstance(stream, AUDIO_INPUT_MIMETYPE); + // If creation of the AudioRecorder failed, simply end the stream + if (!recorder) { + stream.sendEnd(); + return; + } + // Otherwise, ensure that another audio stream is created after this + // audio stream is closed + recorder.onclose = this.initAudioInput.bind(this); + } + + initKeyboard(): void { + const keyboard = new Guacamole.Keyboard(document); + keyboard.onkeydown = (keysym) => { + this.client?.sendKeyEvent(1, keysym); + }; + keyboard.onkeyup = (keysym) => { + this.client?.sendKeyEvent(0, keysym); + }; + } + + async checkClipboard(): Promise { + try { + if (!this._previousClipboardValue) { + this._previousClipboardValue = await navigator.clipboard.readText(); + return; + } + const newValue = await navigator.clipboard.readText(); + if (newValue !== this._previousClipboardValue) { + console.debug(`authentik/rac: new clipboard value: ${newValue}`); + this._previousClipboardValue = newValue; + this.writeClipboard(newValue); + } + } catch (ex) { + // The error is most likely caused by the document not being in focus + // in which case we can ignore it and just retry + if (ex instanceof DOMException) { + return; + } + console.warn("authentik/rac: error reading clipboard", ex); + } + } + + private writeClipboard(value: string) { + if (!this.client) { + return; + } + const stream = this.client.createClipboardStream("text/plain"); + const writer = new Guacamole.StringWriter(stream); + writer.sendText(value); + writer.sendEnd(); + console.debug("authentik/rac: Sent clipboard"); + } + + render(): TemplateResult { + return html` + ${this.clientState !== GuacClientState.CONNECTED + ? html` + + + ${this.hasConnected + ? html`${this.reconnectingMessage}` + : html`${msg("Connecting...")}`} + + + ` + : html``} +
                    ${this.container}
                    + `; + } +} diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts index f7fc80a372..8c1a2afb76 100644 --- a/web/src/flow/FlowExecutor.ts +++ b/web/src/flow/FlowExecutor.ts @@ -8,17 +8,17 @@ import { globalAK } from "@goauthentik/common/global"; import { configureSentry } from "@goauthentik/common/sentry"; import { first } from "@goauthentik/common/utils"; import { WebsocketClient } from "@goauthentik/common/ws"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/LoadingOverlay"; import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/flow/sources/apple/AppleLoginInit"; import "@goauthentik/flow/sources/plex/PlexLoginInit"; import "@goauthentik/flow/stages/FlowErrorStage"; import "@goauthentik/flow/stages/RedirectStage"; -import { StageHost } from "@goauthentik/flow/stages/base"; +import { StageHost, SubmitOptions } from "@goauthentik/flow/stages/base"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html, nothing } from "lit"; +import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { until } from "lit/directives/until.js"; @@ -32,13 +32,15 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { + CapabilitiesEnum, ChallengeChoices, ChallengeTypes, ContextualFlowInfo, + FetchError, FlowChallengeResponseRequest, FlowErrorChallenge, + FlowLayoutEnum, FlowsApi, - LayoutEnum, ResponseError, ShellChallenge, UiThemeEnum, @@ -55,9 +57,9 @@ export class FlowExecutor extends Interface implements StageHost { set challenge(value: ChallengeTypes | undefined) { this._challenge = value; if (value?.flowInfo?.title) { - document.title = `${value.flowInfo?.title} - ${this.tenant?.brandingTitle}`; + document.title = `${value.flowInfo?.title} - ${this.brand?.brandingTitle}`; } else { - document.title = this.tenant?.brandingTitle || TITLE_DEFAULT; + document.title = this.brand?.brandingTitle || TITLE_DEFAULT; } this.requestUpdate(); } @@ -72,24 +74,8 @@ export class FlowExecutor extends Interface implements StageHost { @state() inspectorOpen = false; - _flowInfo?: ContextualFlowInfo; - @state() - set flowInfo(value: ContextualFlowInfo | undefined) { - this._flowInfo = value; - if (!value) { - return; - } - this.shadowRoot - ?.querySelectorAll(".pf-c-background-image") - .forEach((bg) => { - bg.style.setProperty("--ak-flow-background", `url('${value?.background}')`); - }); - } - - get flowInfo(): ContextualFlowInfo | undefined { - return this._flowInfo; - } + flowInfo?: ContextualFlowInfo; ws: WebsocketClient; @@ -115,8 +101,10 @@ export class FlowExecutor extends Interface implements StageHost { background-color: transparent; } /* layouts */ - .pf-c-login.stacked .pf-c-login__main { - margin-top: 13rem; + @media (min-height: 60rem) { + .pf-c-login.stacked .pf-c-login__main { + margin-top: 13rem; + } } .pf-c-login__container.content-right { grid-template-areas: @@ -176,7 +164,7 @@ export class FlowExecutor extends Interface implements StageHost { super(); this.ws = new WebsocketClient(); if (window.location.search.includes("inspector")) { - this.inspectorOpen = !this.inspectorOpen; + this.inspectorOpen = true; } this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => { this.inspectorOpen = !this.inspectorOpen; @@ -184,15 +172,20 @@ export class FlowExecutor extends Interface implements StageHost { } async getTheme(): Promise { - return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic; + return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic; } - async submit(payload?: FlowChallengeResponseRequest): Promise { + async submit( + payload?: FlowChallengeResponseRequest, + options?: SubmitOptions, + ): Promise { if (!payload) return Promise.reject(); if (!this.challenge) return Promise.reject(); - // @ts-ignore + // @ts-expect-error payload.component = this.challenge.component; - this.loading = true; + if (!options?.invisible) { + this.loading = true; + } try { const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({ flowSlug: this.flowSlug, @@ -211,12 +204,9 @@ export class FlowExecutor extends Interface implements StageHost { if (this.challenge.flowInfo) { this.flowInfo = this.challenge.flowInfo; } - if (this.challenge.responseErrors) { - return false; - } - return true; + return !this.challenge.responseErrors; } catch (exc: unknown) { - this.errorMessage(exc as Error | ResponseError); + this.errorMessage(exc as Error | ResponseError | FetchError); return false; } finally { this.loading = false; @@ -225,6 +215,9 @@ export class FlowExecutor extends Interface implements StageHost { async firstUpdated(): Promise { configureSentry(); + if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) { + this.inspectorOpen = true; + } this.loading = true; try { const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({ @@ -245,15 +238,17 @@ export class FlowExecutor extends Interface implements StageHost { } } catch (exc: unknown) { // Catch JSON or Update errors - this.errorMessage(exc as Error | ResponseError); + this.errorMessage(exc as Error | ResponseError | FetchError); } finally { this.loading = false; } } - async errorMessage(error: Error | ResponseError): Promise { + async errorMessage(error: Error | ResponseError | FetchError): Promise { let body = ""; - if (error instanceof ResponseError) { + if (error instanceof FetchError) { + body = msg("Request failed. Please try again later."); + } else if (error instanceof ResponseError) { body = await error.response.text(); } else if (error instanceof Error) { body = error.message; @@ -267,6 +262,24 @@ export class FlowExecutor extends Interface implements StageHost { this.challenge = challenge as ChallengeTypes; } + setShadowStyles(value: ContextualFlowInfo) { + if (!value) { + return; + } + this.shadowRoot + ?.querySelectorAll(".pf-c-background-image") + .forEach((bg) => { + bg.style.setProperty("--ak-flow-background", `url('${value?.background}')`); + }); + } + + // DOM post-processing has to happen after the render. + updated(changedProperties: PropertyValues) { + if (changedProperties.has("flowInfo") && this.flowInfo !== undefined) { + this.setShadowStyles(this.flowInfo); + } + } + async renderChallengeNativeElement(): Promise { switch (this.challenge?.component) { case "ak-stage-access-denied": @@ -405,7 +418,8 @@ export class FlowExecutor extends Interface implements StageHost { async renderChallenge(): Promise { if (!this.challenge) { - return html``; + return html` + `; } switch (this.challenge.type) { case ChallengeChoices.Redirect: @@ -421,14 +435,16 @@ export class FlowExecutor extends Interface implements StageHost { return await this.renderChallengeNativeElement(); default: console.debug(`authentik/flows: unexpected data type ${this.challenge.type}`); - break; + return html``; } - return html``; } renderChallengeWrapper(): TemplateResult { const logo = html``; if (!this.challenge) { return html`${logo} @@ -451,7 +467,7 @@ export class FlowExecutor extends Interface implements StageHost { } getLayout(): string { - const prefilledFlow = globalAK()?.flow?.layout || LayoutEnum.Stacked; + const prefilledFlow = globalAK()?.flow?.layout || FlowLayoutEnum.Stacked; if (this.challenge) { return this.challenge?.flowInfo?.layout || prefilledFlow; } @@ -461,11 +477,11 @@ export class FlowExecutor extends Interface implements StageHost { getLayoutClass(): string { const layout = this.getLayout(); switch (layout) { - case LayoutEnum.ContentLeft: + case FlowLayoutEnum.ContentLeft: return "pf-c-login__container"; - case LayoutEnum.ContentRight: + case FlowLayoutEnum.ContentRight: return "pf-c-login__container content-right"; - case LayoutEnum.Stacked: + case FlowLayoutEnum.Stacked: default: return "ak-login-container"; } @@ -486,7 +502,7 @@ export class FlowExecutor extends Interface implements StageHost {
                      - ${this.tenant?.uiFooterLinks?.map((link) => { + ${this.brand?.uiFooterLinks?.map((link) => { return html`
                    • ${link.name} ${msg("Background image")}
                    • diff --git a/web/src/flow/FlowInspector.ts b/web/src/flow/FlowInspector.ts index 377fc4ad9c..0d15d444ed 100644 --- a/web/src/flow/FlowInspector.ts +++ b/web/src/flow/FlowInspector.ts @@ -15,7 +15,7 @@ import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { FlowInspection, FlowsApi, Stage } from "@goauthentik/api"; +import { FlowInspection, FlowsApi, ResponseError, Stage } from "@goauthentik/api"; @customElement("ak-flow-inspector") export class FlowInspector extends AKElement { @@ -25,7 +25,7 @@ export class FlowInspector extends AKElement { state?: FlowInspection; @property({ attribute: false }) - error?: Response; + error?: ResponseError; static get styles(): CSSResult[] { return [ @@ -37,6 +37,10 @@ export class FlowInspector extends AKElement { PFDescriptionList, PFProgressStepper, css` + .pf-c-drawer__body { + min-height: 100vh; + max-height: 100vh; + } code.break { word-break: break-all; } @@ -45,9 +49,6 @@ export class FlowInspector extends AKElement { overflow-x: hidden; white-space: break-spaces; } - .pf-c-notification-drawer__body { - overflow-x: hidden; - } `, ]; } @@ -69,6 +70,7 @@ export class FlowInspector extends AKElement { flowSlug: this.flowSlug, }) .then((state) => { + this.error = undefined; this.state = state; }) .catch((exc) => { @@ -99,7 +101,7 @@ export class FlowInspector extends AKElement {
                      -
                      ${this.error?.statusText}
                      +
                      ${this.error?.message}
                      @@ -113,6 +115,7 @@ export class FlowInspector extends AKElement { return this.renderAccessDenied(); } if (!this.state) { + this.advanceHandler(); return html` `; } diff --git a/web/src/flow/sources/plex/PlexLoginInit.ts b/web/src/flow/sources/plex/PlexLoginInit.ts index 7ac10a648d..aea8689a53 100644 --- a/web/src/flow/sources/plex/PlexLoginInit.ts +++ b/web/src/flow/sources/plex/PlexLoginInit.ts @@ -10,6 +10,7 @@ import { TemplateResult, html } from "lit"; import { customElement, state } from "lit/decorators.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFDivider from "@patternfly/patternfly/components/Divider/divider.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFLogin from "@patternfly/patternfly/components/Login/login.css"; @@ -32,7 +33,7 @@ export class PlexLoginInit extends BaseStage< authUrl?: string; static get styles(): CSSResult[] { - return [PFBase, PFLogin, PFForm, PFFormControl, PFButton, PFTitle]; + return [PFBase, PFLogin, PFForm, PFFormControl, PFButton, PFTitle, PFDivider]; } async firstUpdated(): Promise { @@ -76,7 +77,7 @@ export class PlexLoginInit extends BaseStage< header=${msg("Waiting for authentication...")} > -
                      +

                      ${msg("If no Plex popup opens, click the button below.")}

                    - - + + ${this.challenge.errorMessage + ? html` +
                    +

                    ${this.challenge.errorMessage}

                    +
                    + ` + : nothing} +
                    diff --git a/web/src/flow/stages/authenticator_duo/AuthenticatorDuoStage.ts b/web/src/flow/stages/authenticator_duo/AuthenticatorDuoStage.ts index 2a8cd82c89..eb28ddd7a9 100644 --- a/web/src/flow/stages/authenticator_duo/AuthenticatorDuoStage.ts +++ b/web/src/flow/stages/authenticator_duo/AuthenticatorDuoStage.ts @@ -5,7 +5,7 @@ import "@goauthentik/flow/FormStatic"; import { BaseStage } from "@goauthentik/flow/stages/base"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -32,14 +32,16 @@ export class AuthenticatorDuoStage extends BaseStage< return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton]; } - firstUpdated(): void { - const i = setInterval(() => { - this.checkEnrollStatus().then((shouldStop) => { - if (shouldStop) { - clearInterval(i); - } - }); - }, 3000); + updated(changedProperties: PropertyValues) { + if (changedProperties.has("challenge") && this.challenge !== undefined) { + const i = setInterval(() => { + this.checkEnrollStatus().then((shouldStop) => { + if (shouldStop) { + clearInterval(i); + } + }); + }, 3000); + } } async checkEnrollStatus(): Promise { diff --git a/web/src/flow/stages/authenticator_static/AuthenticatorStaticStage.ts b/web/src/flow/stages/authenticator_static/AuthenticatorStaticStage.ts index a9975e8737..ffa8a439d8 100644 --- a/web/src/flow/stages/authenticator_static/AuthenticatorStaticStage.ts +++ b/web/src/flow/stages/authenticator_static/AuthenticatorStaticStage.ts @@ -40,6 +40,7 @@ export class AuthenticatorStaticStage extends BaseStage< columns: 2; -webkit-columns: 2; -moz-columns: 2; + column-width: 1em; margin-left: var(--pf-global--spacer--xs); } ul li { diff --git a/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.stories.ts b/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.stories.ts new file mode 100644 index 0000000000..24a4f4a4a8 --- /dev/null +++ b/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.stories.ts @@ -0,0 +1,58 @@ +import type { StoryObj } from "@storybook/web-components"; + +import { html } from "lit"; + +import "@patternfly/patternfly/components/Login/login.css"; + +import { AuthenticatorTOTPChallenge, ChallengeChoices, UiThemeEnum } from "@goauthentik/api"; + +import "../../../stories/flow-interface"; +import "./AuthenticatorTOTPStage"; + +export default { + title: "Flow / Stages / AuthenticatorTOTPStage", +}; + +export const LoadingNoChallenge = () => { + return html` + + `; +}; + +export const Challenge: StoryObj = { + render: ({ theme, challenge }) => { + return html` + `; + }, + args: { + theme: "automatic", + challenge: { + type: ChallengeChoices.Native, + pendingUser: "foo", + pendingUserAvatar: "https://picsum.photos/64", + configUrl: "", + } as AuthenticatorTOTPChallenge, + }, + argTypes: { + theme: { + options: [UiThemeEnum.Automatic, UiThemeEnum.Light, UiThemeEnum.Dark], + control: { + type: "select", + }, + }, + }, +}; diff --git a/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.ts b/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.ts index 38030c10a0..074adb29ca 100644 --- a/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.ts +++ b/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.ts @@ -106,6 +106,11 @@ export class AuthenticatorTOTPStage extends BaseStage<
                    +

                    + ${msg( + "Please scan the QR code above using the Microsoft Authenticator, Google Authenticator, or other authenticator apps on your device, and enter the code the device displays below to finish setting up the MFA device.", + )} +

                    { - return this.host?.submit(payload) || Promise.resolve(); + submit( + payload: AuthenticatorValidationChallengeResponseRequest, + options?: SubmitOptions, + ): Promise { + return this.host?.submit(payload, options) || Promise.resolve(); } static get styles(): CSSResult[] { @@ -89,6 +91,9 @@ export class AuthenticatorValidateStage display: flex; align-items: center; } + :host([theme="dark"]) .authenticator-button { + color: var(--ak-dark-foreground) !important; + } i { font-size: 1.5rem; padding: 1rem 0; @@ -250,23 +255,7 @@ export class AuthenticatorValidateStage ? this.renderDeviceChallenge() : html``; } } diff --git a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts index 23d9e68f34..b4257d7642 100644 --- a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts +++ b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts @@ -1,20 +1,10 @@ import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/forms/FormElement"; -import "@goauthentik/flow/FormStatic"; -import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage"; -import { BaseStage } from "@goauthentik/flow/stages/base"; +import { BaseDeviceStage } from "@goauthentik/flow/stages/authenticator_validate/base"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; -import { ifDefined } from "lit/directives/if-defined.js"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; -import PFLogin from "@patternfly/patternfly/components/Login/login.css"; -import PFTitle from "@patternfly/patternfly/components/Title/title.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import { PropertyValues, TemplateResult, html } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; import { AuthenticatorValidationChallenge, @@ -23,7 +13,7 @@ import { } from "@goauthentik/api"; @customElement("ak-stage-authenticator-validate-duo") -export class AuthenticatorValidateStageWebDuo extends BaseStage< +export class AuthenticatorValidateStageWebDuo extends BaseDeviceStage< AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest > { @@ -33,14 +23,26 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage< @property({ type: Boolean }) showBackButton = false; - static get styles(): CSSResult[] { - return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton]; - } + @state() + authenticating = false; - firstUpdated(): void { - this.host?.submit({ - duo: this.deviceChallenge?.deviceUid, - }); + updated(changedProperties: PropertyValues) { + if (changedProperties.has("challenge") && this.challenge !== undefined) { + this.authenticating = true; + this.host + ?.submit( + { + duo: this.deviceChallenge?.deviceUid, + }, + { invisible: true }, + ) + .then(() => { + this.authenticating = false; + }) + .catch(() => { + this.authenticating = false; + }); + } } render(): TemplateResult { @@ -49,56 +51,25 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage< `; } const errors = this.challenge.responseErrors?.duo || []; + const errorMessage = errors.map((err) => err.string); return html` -
                    - -
                    `; + +
                    ${this.renderReturnToDevicePicker()}
                    + +
                    `; } } diff --git a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts index 12e9b0a601..711ea0db4d 100644 --- a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts +++ b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts @@ -3,21 +3,12 @@ import { transformAssertionForServer, transformCredentialRequestOptions, } from "@goauthentik/common/helpers/webauthn"; -import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage"; -import { BaseStage } from "@goauthentik/flow/stages/base"; +import "@goauthentik/elements/EmptyState"; +import { BaseDeviceStage } from "@goauthentik/flow/stages/authenticator_validate/base"; -import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; -import PFForm from "@patternfly/patternfly/components/Form/form.css"; -import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; -import PFLogin from "@patternfly/patternfly/components/Login/login.css"; -import PFTitle from "@patternfly/patternfly/components/Title/title.css"; -import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import { msg } from "@lit/localize"; +import { PropertyValues, TemplateResult, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; import { AuthenticatorValidationChallenge, @@ -26,7 +17,7 @@ import { } from "@goauthentik/api"; @customElement("ak-stage-authenticator-validate-webauthn") -export class AuthenticatorValidateStageWebAuthn extends BaseStage< +export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage< AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest > { @@ -34,25 +25,15 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage< deviceChallenge?: DeviceChallenge; @property() - authenticateMessage?: string; + errorMessage?: string; @property({ type: Boolean }) showBackButton = false; - transformedCredentialRequestOptions?: PublicKeyCredentialRequestOptions; + @state() + authenticating = false; - static get styles(): CSSResult[] { - return [ - PFBase, - PFLogin, - PFEmptyState, - PFBullseye, - PFForm, - PFFormControl, - PFTitle, - PFButton, - ]; - } + transformedCredentialRequestOptions?: PublicKeyCredentialRequestOptions; async authenticate(): Promise { // request the authenticator to create an assertion signature using the @@ -64,10 +45,10 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage< publicKey: this.transformedCredentialRequestOptions, }); if (!assertion) { - throw new Error(msg("Assertions is empty")); + throw new Error("Assertions is empty"); } } catch (err) { - throw new Error(msg(str`Error when creating credential: ${err}`)); + throw new Error(`Error when creating credential: ${err}`); } // we now have an authentication assertion! encode the byte arrays contained @@ -78,77 +59,73 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage< // post the assertion to the server for verification. try { - await this.host?.submit({ - webauthn: transformedAssertionForServer, - }); + await this.host?.submit( + { + webauthn: transformedAssertionForServer, + }, + { + invisible: true, + }, + ); } catch (err) { - throw new Error(msg(str`Error when validating assertion on server: ${err}`)); + throw new Error(`Error when validating assertion on server: ${err}`); } } - firstUpdated(): void { - // convert certain members of the PublicKeyCredentialRequestOptions into - // byte arrays as expected by the spec. - const credentialRequestOptions = this.deviceChallenge - ?.challenge as PublicKeyCredentialRequestOptions; - this.transformedCredentialRequestOptions = - transformCredentialRequestOptions(credentialRequestOptions); - this.authenticateWrapper(); + updated(changedProperties: PropertyValues) { + if (changedProperties.has("challenge") && this.challenge !== undefined) { + // convert certain members of the PublicKeyCredentialRequestOptions into + // byte arrays as expected by the spec. + const credentialRequestOptions = this.deviceChallenge + ?.challenge as PublicKeyCredentialRequestOptions; + this.transformedCredentialRequestOptions = + transformCredentialRequestOptions(credentialRequestOptions); + this.authenticateWrapper(); + } } async authenticateWrapper(): Promise { - if (this.host.loading) { + if (this.authenticating) { return; } - this.host.loading = true; + this.authenticating = true; this.authenticate() - .catch((e) => { - console.error(e); - this.authenticateMessage = e.toString(); + .catch((e: Error) => { + console.warn("authentik/flows/authenticator_validate/webauthn: failed to auth", e); + this.errorMessage = msg("Authentication failed. Please try again."); }) .finally(() => { - this.host.loading = false; + this.authenticating = false; }); } render(): TemplateResult { return html` -
                    - -
                    `; + ` + : nothing} + ${this.renderReturnToDevicePicker()} +
                    + + `; } } diff --git a/web/src/flow/stages/authenticator_validate/base.ts b/web/src/flow/stages/authenticator_validate/base.ts new file mode 100644 index 0000000000..821014a931 --- /dev/null +++ b/web/src/flow/stages/authenticator_validate/base.ts @@ -0,0 +1,65 @@ +import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage"; +import { BaseStage, FlowInfoChallenge, PendingUserChallenge } from "@goauthentik/flow/stages/base"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, css, html } from "lit"; +import { property } from "lit/decorators.js"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFLogin from "@patternfly/patternfly/components/Login/login.css"; +import PFTitle from "@patternfly/patternfly/components/Title/title.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { DeviceChallenge } from "@goauthentik/api"; + +export class BaseDeviceStage< + Tin extends FlowInfoChallenge & PendingUserChallenge, + Tout, +> extends BaseStage { + @property({ attribute: false }) + deviceChallenge?: DeviceChallenge; + + @property({ type: Boolean }) + showBackButton = false; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFLogin, + PFForm, + PFFormControl, + PFTitle, + PFButton, + css` + .pf-c-form__group.pf-m-action { + display: flex; + gap: 16px; + margin-top: 0; + margin-bottom: calc(var(--pf-c-form__group--m-action--MarginTop) / 2); + flex-direction: column; + } + `, + ]; + } + + submit(payload: Tin): Promise { + return this.host?.submit(payload) || Promise.resolve(); + } + + renderReturnToDevicePicker(): TemplateResult { + if (!this.showBackButton) { + return html``; + } + return html``; + } +} diff --git a/web/src/flow/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts b/web/src/flow/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts index dd056da66b..b2eb9e12c3 100644 --- a/web/src/flow/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts +++ b/web/src/flow/stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage.ts @@ -4,12 +4,13 @@ import { transformCredentialCreateOptions, transformNewAssertionForServer, } from "@goauthentik/common/helpers/webauthn"; -import { PFSize } from "@goauthentik/elements/Spinner"; +import "@goauthentik/elements/EmptyState"; import { BaseStage } from "@goauthentik/flow/stages/base"; import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; @@ -41,7 +42,24 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage< publicKeyCredentialCreateOptions?: PublicKeyCredentialCreationOptions; static get styles(): CSSResult[] { - return [PFBase, PFLogin, PFFormControl, PFForm, PFTitle, PFButton]; + return [ + PFBase, + PFLogin, + PFFormControl, + PFForm, + PFTitle, + PFButton, + // FIXME: this is technically duplicate with ../authenticator_validate/base.ts + css` + .pf-c-form__group.pf-m-action { + display: flex; + gap: 16px; + margin-top: 0; + margin-bottom: calc(var(--pf-c-form__group--m-action--MarginTop) / 2); + flex-direction: column; + } + `, + ]; } async register(): Promise { @@ -69,9 +87,14 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage< // post the transformed credential data to the server for validation // and storing the public key try { - await this.host?.submit({ - response: newAssertionForServer, - }); + await this.host?.submit( + { + response: newAssertionForServer, + }, + { + invisible: true, + }, + ); } catch (err) { throw new Error(msg(str`Server validation of credential failed: ${err}`)); } @@ -84,62 +107,70 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage< this.registerRunning = true; this.register() .catch((e) => { - console.error(e); - this.registerMessage = e.toString(); + console.warn("authentik/flows/authenticator_webauthn: failed to register", e); + this.registerMessage = msg("Failed to register. Please try again."); }) .finally(() => { this.registerRunning = false; }); } - firstUpdated(): void { - // convert certain members of the PublicKeyCredentialCreateOptions into - // byte arrays as expected by the spec. - this.publicKeyCredentialCreateOptions = transformCredentialCreateOptions( - this.challenge?.registration as PublicKeyCredentialCreationOptions, - this.challenge?.registration.user.id, - ); - this.registerWrapper(); + updated(changedProperties: PropertyValues) { + if (changedProperties.has("challenge") && this.challenge !== undefined) { + // convert certain members of the PublicKeyCredentialCreateOptions into + // byte arrays as expected by the spec. + this.publicKeyCredentialCreateOptions = transformCredentialCreateOptions( + this.challenge?.registration as PublicKeyCredentialCreationOptions, + this.challenge?.registration.user.id, + ); + this.registerWrapper(); + } } render(): TemplateResult { return html` - -
                    - -
                    `; + ${msg("Retry registration")} + ` + : nothing} + + + `; } } diff --git a/web/src/flow/stages/base.ts b/web/src/flow/stages/base.ts index 90a5685921..5606527d46 100644 --- a/web/src/flow/stages/base.ts +++ b/web/src/flow/stages/base.ts @@ -1,18 +1,24 @@ import { AKElement } from "@goauthentik/elements/Base"; import { KeyUnknown } from "@goauthentik/elements/forms/Form"; +import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; -import { CurrentTenant, ErrorDetail } from "@goauthentik/api"; +import { ContextualFlowInfo, CurrentBrand, ErrorDetail } from "@goauthentik/api"; + +export interface SubmitOptions { + invisible: boolean; +} export interface StageHost { challenge?: unknown; flowSlug?: string; loading: boolean; - submit(payload: unknown): Promise; + submit(payload: unknown, options?: SubmitOptions): Promise; - readonly tenant?: CurrentTenant; + readonly brand?: CurrentBrand; } export function readFileAsync(file: Blob) { @@ -26,7 +32,21 @@ export function readFileAsync(file: Blob) { }); } -export class BaseStage extends AKElement { +// Challenge which contains flow info +export interface FlowInfoChallenge { + flowInfo?: ContextualFlowInfo; +} + +// Challenge which has a pending user +export interface PendingUserChallenge { + pendingUser?: string; + pendingUserAvatar?: string; +} + +export class BaseStage< + Tin extends FlowInfoChallenge & PendingUserChallenge, + Tout, +> extends AKElement { host!: StageHost; @property({ attribute: false }) @@ -68,6 +88,31 @@ export class BaseStage extends AKElement { `; } + renderUserInfo(): TemplateResult { + if (!this.challenge.pendingUser || !this.challenge.pendingUserAvatar) { + return html``; + } + return html` + + + + + `; + } + cleanup(): void { // Method that can be overridden by stages return; diff --git a/web/src/flow/stages/captcha/CaptchaStage.stories.ts b/web/src/flow/stages/captcha/CaptchaStage.stories.ts new file mode 100644 index 0000000000..668eaf1b59 --- /dev/null +++ b/web/src/flow/stages/captcha/CaptchaStage.stories.ts @@ -0,0 +1,119 @@ +import type { StoryObj } from "@storybook/web-components"; + +import { html } from "lit"; + +import "@patternfly/patternfly/components/Login/login.css"; + +import { CaptchaChallenge, ChallengeChoices, UiThemeEnum } from "@goauthentik/api"; + +import "../../../stories/flow-interface"; +import "./CaptchaStage"; + +export default { + title: "Flow / Stages / CaptchaStage", +}; + +export const LoadingNoChallenge = () => { + return html` + + `; +}; + +export const ChallengeGoogleReCaptcha: StoryObj = { + render: ({ theme, challenge }) => { + return html` + `; + }, + args: { + theme: "automatic", + challenge: { + type: ChallengeChoices.Native, + pendingUser: "foo", + pendingUserAvatar: "https://picsum.photos/64", + jsUrl: "https://www.google.com/recaptcha/api.js", + siteKey: "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI", + } as CaptchaChallenge, + }, + argTypes: { + theme: { + options: [UiThemeEnum.Automatic, UiThemeEnum.Light, UiThemeEnum.Dark], + control: { + type: "select", + }, + }, + }, +}; + +export const ChallengeHCaptcha: StoryObj = { + render: ({ theme, challenge }) => { + return html` + `; + }, + args: { + theme: "automatic", + challenge: { + type: ChallengeChoices.Native, + pendingUser: "foo", + pendingUserAvatar: "https://picsum.photos/64", + jsUrl: "https://js.hcaptcha.com/1/api.js", + siteKey: "10000000-ffff-ffff-ffff-000000000001", + } as CaptchaChallenge, + }, + argTypes: { + theme: { + options: [UiThemeEnum.Automatic, UiThemeEnum.Light, UiThemeEnum.Dark], + control: { + type: "select", + }, + }, + }, +}; + +export const ChallengeTurnstile: StoryObj = { + render: ({ theme, challenge }) => { + return html` + `; + }, + args: { + theme: "automatic", + challenge: { + type: ChallengeChoices.Native, + pendingUser: "foo", + pendingUserAvatar: "https://picsum.photos/64", + jsUrl: "https://challenges.cloudflare.com/turnstile/v0/api.js", + siteKey: "1x00000000000000000000BB", + } as CaptchaChallenge, + }, + argTypes: { + theme: { + options: [UiThemeEnum.Automatic, UiThemeEnum.Light, UiThemeEnum.Dark], + control: { + type: "select", + }, + }, + }, +}; diff --git a/web/src/flow/stages/captcha/CaptchaStage.ts b/web/src/flow/stages/captcha/CaptchaStage.ts index 753bda99c0..199961b836 100644 --- a/web/src/flow/stages/captcha/CaptchaStage.ts +++ b/web/src/flow/stages/captcha/CaptchaStage.ts @@ -1,14 +1,12 @@ /// import "@goauthentik/elements/EmptyState"; -import { PFSize } from "@goauthentik/elements/Spinner"; import "@goauthentik/elements/forms/FormElement"; import "@goauthentik/flow/FormStatic"; -import "@goauthentik/flow/stages/access_denied/AccessDeniedStage"; import { BaseStage } from "@goauthentik/flow/stages/base"; import type { TurnstileObject } from "turnstile-types"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; import { customElement, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -25,6 +23,8 @@ interface TurnstileWindow extends Window { turnstile: TurnstileObject; } +const captchaContainerID = "captcha-container"; + @customElement("ak-stage-captcha") export class CaptchaStage extends BaseStage { static get styles(): CSSResult[] { @@ -36,50 +36,71 @@ export class CaptchaStage extends BaseStage { - console.debug("authentik/stages/captcha: script loaded"); - let found = false; - let lastError = undefined; - this.handlers.forEach((handler) => { - let handlerFound = false; - try { - console.debug(`authentik/stages/captcha[${handler.name}]: trying handler`); - handlerFound = handler.apply(this, [captchaContainer]); - if (handlerFound) { - console.debug( - `authentik/stages/captcha[${handler.name}]: handler succeeded`, - ); - found = true; - } - } catch (exc) { - console.debug( - `authentik/stages/captcha[${handler.name}]: handler failed: ${exc}`, - ); - if (handlerFound) { - lastError = exc; - } - } - }); - if (!found && lastError) { - this.error = (lastError as Error).toString(); - } - }; - document.head.appendChild(script); + @state() + captchaInteractive: boolean = true; + + @state() + captchaContainer: HTMLDivElement; + + @state() + scriptElement?: HTMLScriptElement; + + constructor() { + super(); + this.captchaContainer = document.createElement("div"); + this.captchaContainer.id = captchaContainerID; } - handleGReCaptcha(container: HTMLDivElement): boolean { + updated(changedProperties: PropertyValues) { + if (changedProperties.has("challenge") && this.challenge !== undefined) { + this.scriptElement = document.createElement("script"); + this.scriptElement.src = this.challenge.jsUrl; + this.scriptElement.async = true; + this.scriptElement.defer = true; + this.scriptElement.dataset.akCaptchaScript = "true"; + this.scriptElement.onload = () => { + console.debug("authentik/stages/captcha: script loaded"); + let found = false; + let lastError = undefined; + this.handlers.forEach((handler) => { + let handlerFound = false; + try { + console.debug(`authentik/stages/captcha[${handler.name}]: trying handler`); + handlerFound = handler.apply(this); + if (handlerFound) { + console.debug( + `authentik/stages/captcha[${handler.name}]: handler succeeded`, + ); + found = true; + } + } catch (exc) { + console.debug( + `authentik/stages/captcha[${handler.name}]: handler failed: ${exc}`, + ); + if (handlerFound) { + lastError = exc; + } + } + }); + if (!found && lastError) { + this.error = (lastError as Error).toString(); + } + }; + document.head + .querySelectorAll("[data-ak-captcha-script=true]") + .forEach((el) => el.remove()); + document.head.appendChild(this.scriptElement); + } + } + + handleGReCaptcha(): boolean { if (!Object.hasOwn(window, "grecaptcha")) { return false; } + this.captchaInteractive = false; + document.body.appendChild(this.captchaContainer); grecaptcha.ready(() => { - const captchaId = grecaptcha.render(container, { + const captchaId = grecaptcha.render(this.captchaContainer, { sitekey: this.challenge.siteKey, callback: (token) => { this.host?.submit({ @@ -93,11 +114,13 @@ export class CaptchaStage extends BaseStage { @@ -110,11 +133,13 @@ export class CaptchaStage extends BaseStage { this.host?.submit({ @@ -125,6 +150,19 @@ export class CaptchaStage extends BaseStage `; + } + if (this.captchaInteractive) { + return html`${this.captchaContainer}`; + } + return html``; + } + render(): TemplateResult { if (!this.challenge) { return html` @@ -146,12 +184,7 @@ export class CaptchaStage extends BaseStage - ${this.error - ? html` - ` - : html`
                    - -
                    `} + ${this.renderBody()}
                    diff --git a/web/src/flow/stages/dummy/DummyStage.ts b/web/src/flow/stages/dummy/DummyStage.ts index 7fb5895a17..6f11b9e83b 100644 --- a/web/src/flow/stages/dummy/DummyStage.ts +++ b/web/src/flow/stages/dummy/DummyStage.ts @@ -2,7 +2,7 @@ import "@goauthentik/elements/EmptyState"; import "@goauthentik/flow/FormStatic"; import { BaseStage } from "@goauthentik/flow/stages/base"; -import { msg } from "@lit/localize"; +import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; @@ -36,6 +36,7 @@ export class DummyStage extends BaseStage +

                    ${msg(str`Stage name: ${this.challenge.name}`)}

                    `; } renderInput(): TemplateResult { let type: "text" | "email" = "text"; if (!this.challenge?.userFields || this.challenge.userFields.length === 0) { - return html`

                    ${msg("Select one of the sources below to login.")}

                    `; + return html`

                    ${msg("Select one of the options below to continue.")}

                    `; } const fields = (this.challenge?.userFields || []).sort(); // Check if the field should be *only* email to set the input type @@ -220,7 +223,16 @@ export class IdentificationStage extends BaseStage< [UserFieldsEnum.Upn]: msg("UPN"), }; const label = OR_LIST_FORMATTERS.format(fields.map((f) => uiFields[f])); - return html` + ${msg( + "Enter the email associated with your account, and we'll send you a link to reset your password.", + )} +

                    + ` + : nothing} + ` - : html``} + : nothing} ${"non_field_errors" in (this.challenge?.responseErrors || {}) ? this.renderNonFieldErrors(this.challenge?.responseErrors?.non_field_errors || []) - : html``} + : nothing}
                    ${this.challenge.passwordlessUrl - ? html`${msg("Or")} - ` - : html``}`; + ? html`${msg("Or")}` + : nothing}`; } render(): TemplateResult { @@ -296,8 +300,20 @@ export class IdentificationStage extends BaseStage< ? html`

                    ${msg(str`Login to continue to ${this.challenge.applicationPre}.`)}

                    ` - : html``} + : nothing} ${this.renderInput()} + ${this.challenge.passwordlessUrl + ? html` + + ` + : nothing}
                    diff --git a/web/src/flow/stages/prompt/PromptStage.ts b/web/src/flow/stages/prompt/PromptStage.ts index 877d021192..09cc6959ee 100644 --- a/web/src/flow/stages/prompt/PromptStage.ts +++ b/web/src/flow/stages/prompt/PromptStage.ts @@ -1,6 +1,9 @@ -import { rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/Divider"; import "@goauthentik/elements/EmptyState"; +import { + CapabilitiesEnum, + WithCapabilitiesConfig, +} from "@goauthentik/elements/Interface/capabilitiesProvider"; import { LOCALES } from "@goauthentik/elements/ak-locale-context/definitions"; import "@goauthentik/elements/forms/FormElement"; import { BaseStage } from "@goauthentik/flow/stages/base"; @@ -20,7 +23,6 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import { - CapabilitiesEnum, PromptChallenge, PromptChallengeResponseRequest, PromptTypeEnum, @@ -28,7 +30,9 @@ import { } from "@goauthentik/api"; @customElement("ak-stage-prompt") -export class PromptStage extends BaseStage { +export class PromptStage extends WithCapabilitiesConfig( + BaseStage, +) { static get styles(): CSSResult[] { return [ PFBase, @@ -193,10 +197,7 @@ ${prompt.initialValue} `; })}`; case PromptTypeEnum.AkLocale: { - const inDebug = rootInterface()?.config?.capabilities.includes( - CapabilitiesEnum.CanDebug, - ); - const locales = inDebug + const locales = this.can(CapabilitiesEnum.CanDebug) ? LOCALES : LOCALES.filter((locale) => locale.code !== "debug"); const options = locales.map( diff --git a/web/src/locale-codes.ts b/web/src/locale-codes.ts index 86337dc8d0..8b7a36ac15 100644 --- a/web/src/locale-codes.ts +++ b/web/src/locale-codes.ts @@ -15,10 +15,13 @@ export const targetLocales = [ `en`, `es`, `fr`, + `ko`, + `nl`, `pl`, `pseudo-LOCALE`, `tr`, `zh_TW`, + `zh-CN`, `zh-Hans`, `zh-Hant`, ] as const; @@ -32,10 +35,13 @@ export const allLocales = [ `en`, `es`, `fr`, + `ko`, + `nl`, `pl`, `pseudo-LOCALE`, `tr`, `zh_TW`, + `zh-CN`, `zh-Hans`, `zh-Hant`, ] as const; diff --git a/web/src/standalone/api-browser/index.ts b/web/src/standalone/api-browser/index.ts index b0c5849ed3..703caed24e 100644 --- a/web/src/standalone/api-browser/index.ts +++ b/web/src/standalone/api-browser/index.ts @@ -2,9 +2,9 @@ import { CSRFHeaderName } from "@goauthentik/common/api/middleware"; import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants"; import { globalAK } from "@goauthentik/common/global"; import { first, getCookie } from "@goauthentik/common/utils"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/ak-locale-context"; -import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand"; +import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand"; import "rapidoc"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -59,7 +59,7 @@ export class APIBrowser extends Interface { } async getTheme(): Promise { - return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic; + return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic; } render(): TemplateResult { @@ -103,7 +103,7 @@ export class APIBrowser extends Interface { diff --git a/web/src/standalone/loading/index.ts b/web/src/standalone/loading/index.ts index 907a051403..e1d54f323c 100644 --- a/web/src/standalone/loading/index.ts +++ b/web/src/standalone/loading/index.ts @@ -1,5 +1,5 @@ import { globalAK } from "@goauthentik/common/global"; -import { Interface } from "@goauthentik/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -29,7 +29,7 @@ export class Loading extends Interface { } async getTheme(): Promise { - return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic; + return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic; } render(): TemplateResult { diff --git a/web/src/stories/flow-interface.ts b/web/src/stories/flow-interface.ts index 3975e29f7a..7dbe53ae35 100644 --- a/web/src/stories/flow-interface.ts +++ b/web/src/stories/flow-interface.ts @@ -1,4 +1,4 @@ -import { FlowExecutor } from "@goauthentik/app/flow/FlowExecutor"; +import { FlowExecutor } from "@goauthentik/flow/FlowExecutor"; import { customElement, property } from "lit/decorators.js"; diff --git a/web/src/stories/interface.ts b/web/src/stories/interface.ts index c4e2dc03d6..ec15e6ad83 100644 --- a/web/src/stories/interface.ts +++ b/web/src/stories/interface.ts @@ -1,4 +1,4 @@ -import { Interface } from "@goauthentik/app/elements/Base"; +import { Interface } from "@goauthentik/elements/Interface"; import { customElement, property } from "lit/decorators.js"; diff --git a/web/src/user/LibraryApplication/RACLaunchEndpointModal.ts b/web/src/user/LibraryApplication/RACLaunchEndpointModal.ts new file mode 100644 index 0000000000..cf643aeaaa --- /dev/null +++ b/web/src/user/LibraryApplication/RACLaunchEndpointModal.ts @@ -0,0 +1,71 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { PaginatedResponse, TableColumn } from "@goauthentik/elements/table/Table"; +import { TableModal } from "@goauthentik/elements/table/TableModal"; + +import { msg } from "@lit/localize"; +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { Application, Endpoint, RacApi } from "@goauthentik/api"; + +@customElement("ak-library-rac-endpoint-launch") +export class RACLaunchEndpointModal extends TableModal { + clickable = true; + searchEnabled(): boolean { + return true; + } + + clickHandler = (item: Endpoint) => { + if (!item.launchUrl) { + return; + } + if (this.app?.openInNewTab) { + window.open(item.launchUrl); + } else { + window.location.assign(item.launchUrl); + } + }; + + @property({ attribute: false }) + app?: Application; + + async apiEndpoint(page: number): Promise> { + const endpoints = await new RacApi(DEFAULT_CONFIG).racEndpointsList({ + provider: this.app?.provider || 0, + page: page, + search: this.search, + }); + if (this.open && endpoints.pagination.count === 1) { + this.clickHandler(endpoints.results[0]); + this.open = false; + } + return endpoints; + } + + columns(): TableColumn[] { + return [new TableColumn("Name")]; + } + + row(item: Endpoint): TemplateResult[] { + return [html`${item.name}`]; + } + + renderModalInner(): TemplateResult { + return html`
                    +
                    +

                    ${msg("Select endpoint to connect to")}

                    +
                    +
                    +
                    ${this.renderTable()}
                    +
                    + { + this.open = false; + }} + class="pf-m-secondary" + > + ${msg("Cancel")} + +
                    `; + } +} diff --git a/web/src/user/LibraryApplication/index.ts b/web/src/user/LibraryApplication/index.ts index 282ce63b28..30475dd981 100644 --- a/web/src/user/LibraryApplication/index.ts +++ b/web/src/user/LibraryApplication/index.ts @@ -1,8 +1,9 @@ -import { PFSize } from "@goauthentik/app/elements/Spinner"; +import { PFSize } from "@goauthentik/common/enums.js"; import { truncateWords } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-app-icon"; import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/Expand"; +import "@goauthentik/user/LibraryApplication/RACLaunchEndpointModal"; import { UserInterface } from "@goauthentik/user/UserInterface"; import { msg } from "@lit/localize"; @@ -85,6 +86,22 @@ export class LibraryApplication extends AKElement { `; } + renderLaunch(): TemplateResult { + if (!this.application) { + return html``; + } + if (this.application?.launchUrl === "goauthentik.io://providers/rac/launch") { + return html` + ${this.application.name} + `; + } + return html`${this.application.name}`; + } + render(): TemplateResult { if (!this.application) { return html``; @@ -111,13 +128,7 @@ export class LibraryApplication extends AKElement { - +
                    ${this.renderLaunch()}
                    ${expandable ? this.renderExpansion(this.application) : nothing} `; diff --git a/web/src/user/LibraryPage/ApplicationEmptyState.ts b/web/src/user/LibraryPage/ApplicationEmptyState.ts index 11871c1396..bcd62611aa 100644 --- a/web/src/user/LibraryPage/ApplicationEmptyState.ts +++ b/web/src/user/LibraryPage/ApplicationEmptyState.ts @@ -1,5 +1,4 @@ import { docLink } from "@goauthentik/common/global"; -import { adaptCSS } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; @@ -20,23 +19,23 @@ import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css"; * administrator, provide a link to the "Create a new application" page. */ -const styles = adaptCSS([ - PFBase, - PFEmptyState, - PFButton, - PFContent, - PFSpacing, - css` - .cta { - display: inline-block; - font-weight: bold; - } - `, -]); - @customElement("ak-library-application-empty-list") export class LibraryPageApplicationEmptyList extends AKElement { - static styles = styles; + static get styles() { + return [ + PFBase, + PFEmptyState, + PFButton, + PFContent, + PFSpacing, + css` + .cta { + display: inline-block; + font-weight: bold; + } + `, + ]; + } @property({ attribute: "isadmin", type: Boolean }) isAdmin = false; diff --git a/web/src/user/LibraryPage/ApplicationList.ts b/web/src/user/LibraryPage/ApplicationList.ts index 7b680d8cac..c51acce659 100644 --- a/web/src/user/LibraryPage/ApplicationList.ts +++ b/web/src/user/LibraryPage/ApplicationList.ts @@ -31,22 +31,22 @@ const LAYOUTS = new Map([ ], ]); -const styles = [ - PFBase, - PFEmptyState, - PFContent, - PFGrid, - css` - .app-group-header { - margin-bottom: 1em; - margin-top: 1.2em; - } - `, -]; - @customElement("ak-library-application-list") export class LibraryPageApplicationList extends AKElement { - static styles = styles; + static get styles() { + return [ + PFBase, + PFEmptyState, + PFContent, + PFGrid, + css` + .app-group-header { + margin-bottom: 1em; + margin-top: 1.2em; + } + `, + ]; + } @property({ attribute: true }) layout = "row" as LayoutType; diff --git a/web/src/user/LibraryPage/ApplicationSearch.ts b/web/src/user/LibraryPage/ApplicationSearch.ts index b780e2ef54..7e6f40f53a 100644 --- a/web/src/user/LibraryPage/ApplicationSearch.ts +++ b/web/src/user/LibraryPage/ApplicationSearch.ts @@ -18,24 +18,29 @@ import { customEvent } from "./helpers"; @customElement("ak-library-list-search") export class LibraryPageApplicationList extends AKElement { - static styles = [ - PFBase, - PFDisplay, - css` - input { - width: 30ch; - box-sizing: border-box; - border: 0; - border-bottom: 1px solid; - border-bottom-color: var(--ak-accent); - background-color: transparent; - font-size: 1.5rem; - } - input:focus { - outline: 0; - } - `, - ]; + static get styles() { + return [ + PFBase, + PFDisplay, + css` + input { + width: 30ch; + box-sizing: border-box; + border: 0; + border-bottom: 1px solid; + border-bottom-color: var(--ak-accent); + background-color: transparent; + font-size: 1.5rem; + } + input:focus { + outline: 0; + } + :host([theme="dark"]) input { + color: var(--ak-dark-foreground) !important; + } + `, + ]; + } @property({ attribute: false }) set apps(value: Application[]) { diff --git a/web/src/user/LibraryPage/LibraryPage.ts b/web/src/user/LibraryPage/LibraryPage.ts index cdd54f8f72..01ab26c718 100644 --- a/web/src/user/LibraryPage/LibraryPage.ts +++ b/web/src/user/LibraryPage/LibraryPage.ts @@ -2,7 +2,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { me } from "@goauthentik/common/users"; import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/EmptyState"; -import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { localized, msg } from "@lit/localize"; import { html } from "lit"; @@ -25,6 +24,8 @@ import type { PageUIConfig } from "./types"; * */ +const coreApi = () => new CoreApi(DEFAULT_CONFIG); + @localized() @customElement("ak-library") export class LibraryPage extends AKElement { @@ -35,15 +36,13 @@ export class LibraryPage extends AKElement { isAdmin = false; @state() - apps!: PaginatedResponse; + apps: Application[] = []; @state() uiConfig: PageUIConfig; constructor() { super(); - const applicationListFetch = new CoreApi(DEFAULT_CONFIG).coreApplicationsList({}); - const meFetch = me(); const uiConfig = rootInterface()?.uiConfig; if (!uiConfig) { throw new Error("Could not retrieve uiConfig. Reason: unknown. Check logs."); @@ -55,22 +54,41 @@ export class LibraryPage extends AKElement { searchEnabled: uiConfig.enabledFeatures.search, }; - Promise.allSettled([applicationListFetch, meFetch]).then( - ([applicationListStatus, meStatus]) => { - if (meStatus.status === "rejected") { - throw new Error( - `Could not determine status of user. Reason: ${meStatus.reason}`, - ); + Promise.all([this.fetchApplications(), me()]).then(([applications, meStatus]) => { + this.isAdmin = meStatus.user.isSuperuser; + this.apps = applications; + this.ready = true; + }); + } + + async fetchApplications(): Promise { + const applicationListParams = (page = 1) => ({ + ordering: "name", + page, + pageSize: 100, + }); + + const applicationListFetch = await coreApi().coreApplicationsList(applicationListParams(1)); + const pageCount = applicationListFetch.pagination.totalPages; + if (pageCount === 1) { + return applicationListFetch.results; + } + + const applicationLaterPages = await Promise.allSettled( + Array.from({ length: pageCount - 1 }).map((_a, idx) => + coreApi().coreApplicationsList(applicationListParams(idx + 2)), + ), + ); + + return applicationLaterPages.reduce( + function (acc, result) { + if (result.status === "rejected") { + const reason = JSON.stringify(result.reason, null, 2); + throw new Error(`Could not retrieve list of applications. Reason: ${reason}`); } - if (applicationListStatus.status === "rejected") { - throw new Error( - `Could not retrieve list of applications. Reason: ${applicationListStatus.reason}`, - ); - } - this.isAdmin = meStatus.value.user.isSuperuser; - this.apps = applicationListStatus.value; - this.ready = true; + return [...acc, ...result.value.results]; }, + [...applicationListFetch.results], ); } diff --git a/web/src/user/LibraryPage/LibraryPageImpl.ts b/web/src/user/LibraryPage/LibraryPageImpl.ts index 79fc8d609f..1892e499f9 100644 --- a/web/src/user/LibraryPage/LibraryPageImpl.ts +++ b/web/src/user/LibraryPage/LibraryPageImpl.ts @@ -1,7 +1,6 @@ import { groupBy } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/EmptyState"; -import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import "@goauthentik/user/LibraryApplication"; import { msg } from "@lit/localize"; @@ -35,13 +34,15 @@ import type { AppGroupList, PageUIConfig } from "./types"; @customElement("ak-library-impl") export class LibraryPage extends AKElement { - static styles = styles; + static get styles() { + return styles; + } @property({ attribute: "isadmin", type: Boolean }) isAdmin = false; - @property({ attribute: false }) - apps!: PaginatedResponse; + @property({ attribute: false, type: Array }) + apps!: Application[]; @property({ attribute: false }) uiConfig!: PageUIConfig; @@ -64,7 +65,7 @@ export class LibraryPage extends AKElement { connectedCallback() { super.connectedCallback(); - this.filteredApps = this.apps?.results; + this.filteredApps = this.apps; if (this.filteredApps === undefined) { throw new Error( "Application.results should never be undefined when passed to the Library Page.", @@ -87,7 +88,7 @@ export class LibraryPage extends AKElement { event.stopPropagation(); const apps = event.detail.apps; this.selectedApp = undefined; - this.filteredApps = this.apps.results; + this.filteredApps = this.apps; if (apps.length > 0) { this.selectedApp = apps[0]; this.filteredApps = event.detail.apps; @@ -130,7 +131,7 @@ export class LibraryPage extends AKElement { } renderSearch() { - return html``; + return html``; } render() { diff --git a/web/src/user/LibraryPage/LibraryPageImpl.utils.ts b/web/src/user/LibraryPage/LibraryPageImpl.utils.ts index bac9186e8f..0b3375dc3e 100644 --- a/web/src/user/LibraryPage/LibraryPageImpl.utils.ts +++ b/web/src/user/LibraryPage/LibraryPageImpl.utils.ts @@ -2,10 +2,16 @@ import type { Application } from "@goauthentik/api"; const isFullUrlRe = new RegExp("://"); const isHttpRe = new RegExp("http(s?)://"); +const isAuthentikSpecialRe = new RegExp("goauthentik.io://"); const isNotFullUrl = (url: string) => !isFullUrlRe.test(url); const isHttp = (url: string) => isHttpRe.test(url); +const isAuthentikSpecial = (url: string) => isAuthentikSpecialRe.test(url); export const appHasLaunchUrl = (app: Application) => { const url = app.launchUrl; - return !!(typeof url === "string" && url !== "" && (isHttp(url) || isNotFullUrl(url))); + return !!( + typeof url === "string" && + url !== "" && + (isHttp(url) || isNotFullUrl(url) || isAuthentikSpecial(url)) + ); }; diff --git a/web/src/user/Routes.ts b/web/src/user/Routes.ts index fa3609654d..d6692221a9 100644 --- a/web/src/user/Routes.ts +++ b/web/src/user/Routes.ts @@ -1,6 +1,6 @@ -import "@goauthentik/app/common/oauth/callback"; -import { userSettings } from "@goauthentik/app/common/oauth/settings"; -import "@goauthentik/app/common/oauth/signout"; +import "@goauthentik/components/oauth/callback"; +import { userSettings } from "@goauthentik/components/oauth/settings"; +import "@goauthentik/components/oauth/signout"; import { Route } from "@goauthentik/elements/router/Route"; import "@goauthentik/user/LibraryPage/LibraryPage"; diff --git a/web/src/user/UserInterface.ts b/web/src/user/UserInterface.ts index 023defe2ea..3e85525674 100644 --- a/web/src/user/UserInterface.ts +++ b/web/src/user/UserInterface.ts @@ -1,5 +1,3 @@ -import { OAuthInterface } from "@goauthentik/app/common/oauth/interface"; -import { userSettings } from "@goauthentik/app/common/oauth/settings"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_API_DRAWER_TOGGLE, @@ -7,12 +5,16 @@ import { EVENT_WS_MESSAGE, } from "@goauthentik/common/constants"; import { configureSentry } from "@goauthentik/common/sentry"; -import { UserDisplay } from "@goauthentik/common/ui/config"; +import { UIConfig, UserDisplay } from "@goauthentik/common/ui/config"; import { me } from "@goauthentik/common/users"; -import { first } from "@goauthentik/common/utils"; import { WebsocketClient } from "@goauthentik/common/ws"; +import { OAuthLoginController } from "@goauthentik/components/oauth/controller.js"; +import { userSettings } from "@goauthentik/components/oauth/settings"; +import { AKElement } from "@goauthentik/elements/Base"; +import { EnterpriseAwareInterface } from "@goauthentik/elements/Interface"; import "@goauthentik/elements/ak-locale-context"; import "@goauthentik/elements/buttons/ActionButton"; +import { bound } from "@goauthentik/elements/decorators/bound.js"; import "@goauthentik/elements/enterprise/EnterpriseStatusBanner"; import "@goauthentik/elements/messages/MessageContainer"; import "@goauthentik/elements/notifications/APIDrawer"; @@ -20,14 +22,14 @@ import "@goauthentik/elements/notifications/NotificationDrawer"; import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch"; import "@goauthentik/elements/router/RouterOutlet"; import "@goauthentik/elements/sidebar/Sidebar"; -import { DefaultTenant } from "@goauthentik/elements/sidebar/SidebarBrand"; +import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand"; import "@goauthentik/elements/sidebar/SidebarItem"; import { ROUTES } from "@goauthentik/user/Routes"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; -import { UserManagerSettings } from "oidc-client-ts"; +import { match } from "ts-pattern"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html } from "lit"; +import { css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; @@ -40,29 +42,86 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; -import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api"; +import { CoreApi, CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api"; -@customElement("ak-interface-user") -export class UserInterface extends OAuthInterface { - @property({ type: Boolean }) - notificationDrawerOpen = getURLParam("notificationDrawerOpen", false); - - @property({ type: Boolean }) - apiDrawerOpen = getURLParam("apiDrawerOpen", false); - - ws: WebsocketClient; - - @property({ type: Number }) - notificationsCount = 0; - - @state() - me?: SessionUser; - - get oauthSettings(): UserManagerSettings { - return userSettings; +const customStyles = css` + .pf-c-page__main, + .pf-c-drawer__content, + .pf-c-page__drawer { + z-index: auto !important; + background-color: transparent !important; } + .pf-c-page__header { + background-color: transparent !important; + box-shadow: none !important; + color: black !important; + } + :host([theme="dark"]) .pf-c-page__header { + color: var(--ak-dark-foreground) !important; + } + :host([theme="light"]) .pf-c-page__header-tools-item .fas, + :host([theme="light"]) .pf-c-notification-badge__count, + :host([theme="light"]) .pf-c-page__header-tools-group .pf-c-button { + color: var(--ak-global--Color--100) !important; + } + .pf-c-page { + background-color: transparent; + } + .display-none { + display: none; + } + .pf-c-brand { + min-height: 32px; + height: 32px; + } + .has-notifications { + color: #2b9af3; + } + .background-wrapper { + height: 100vh; + width: 100%; + position: fixed; + z-index: -1; + top: 0; + left: 0; + background-color: var(--pf-c-page--BackgroundColor) !important; + } + .background-default-slant { + background-color: white; /*var(--ak-accent);*/ + clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 5vw)); + height: 50vh; + } + :host([theme="dark"]) .background-default-slant { + background-color: black; + } + ak-locale-context { + display: flex; + flex-direction: column; + } + .pf-c-drawer__main { + min-height: calc(100vh - 76px); + max-height: calc(100vh - 76px); + } +`; - static get styles(): CSSResult[] { +// ___ _ _ _ +// | _ \_ _ ___ ___ ___ _ _| |_ __ _| |_(_)___ _ _ +// | _/ '_/ -_|_- { - this.notificationDrawerOpen = !this.notificationDrawerOpen; - updateURLParams({ - notificationDrawerOpen: this.notificationDrawerOpen, - }); - }); - window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => { - this.apiDrawerOpen = !this.apiDrawerOpen; - updateURLParams({ - apiDrawerOpen: this.apiDrawerOpen, - }); - }); - window.addEventListener(EVENT_WS_MESSAGE, () => { - this.firstUpdated(new Map()); - }); - configureSentry(true); + @property({ type: Object }) + uiConfig!: UIConfig; + + @property({ type: Object }) + me!: SessionUser; + + @property({ type: Boolean, reflect: true }) + notificationDrawerOpen = false; + + @property({ type: Boolean, reflect: true }) + apiDrawerOpen = false; + + @property({ type: Number }) + notificationsCount = 0; + + @property({ type: Object }) + brand!: CurrentBrand; + + get userDisplayName() { + return match(this.uiConfig.navbar.userDisplay) + .with(UserDisplay.username, () => this.me.user.username) + .with(UserDisplay.name, () => this.me.user.name) + .with(UserDisplay.email, () => this.me.user.email || "") + .otherwise(() => this.me.user.username); } - async firstUpdated(_changedProperties: Map): Promise { - super.firstUpdated(_changedProperties); - this.me = await me(); - const notifications = await new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({ - seen: false, - ordering: "-created", - pageSize: 1, - user: this.me.user.pk, - }); - this.notificationsCount = notifications.pagination.count; - } - - render(): TemplateResult { - if (!this.uiConfig || !this.me) { - return html``; - } - let userDisplay = ""; - switch (this.uiConfig.navbar.userDisplay) { - case UserDisplay.username: - userDisplay = this.me.user.username; - break; - case UserDisplay.name: - userDisplay = this.me.user.name; - break; - case UserDisplay.email: - userDisplay = this.me.user.email || ""; - break; - default: - userDisplay = this.me.user.username; - } - const canAccessAdmin = + get canAccessAdmin() { + return ( this.me.user.isSuperuser || // TODO: somehow add `access_admin_interface` to the API schema - this.me.user.systemPermissions.includes("access_admin_interface"); + this.me.user.systemPermissions.includes("access_admin_interface") + ); + } + + get isFullyConfigured() { + return !!(this.uiConfig && this.me && this.brand); + } + + render() { + // The `!` in the field definitions above only re-assure typescript and eslint that the + // values *should* be available, not that they *are*. Thus this contract check; it asserts + // that the contract we promised is being honored, and the rest of the code that depends on + // `!` being truthful is not being lied to. + if (!this.isFullyConfigured) { + throw new Error("ak-interface-user-presentation misused; no valid values passed"); + } + return html`
                    @@ -199,128 +196,33 @@ export class UserInterface extends OAuthInterface { ${(this.tenant?.brandingTitle, DefaultTenant.brandingTitle)}
                    - ${this.uiConfig.enabledFeatures.apiDrawer - ? html`
                    - -
                    ` - : html``} - ${this.uiConfig.enabledFeatures.notificationDrawer - ? html`
                    - -
                    ` - : html``} - ${this.uiConfig.enabledFeatures.settings - ? html` ` - : html``} + ${this.renderApiDrawerTrigger()} + + ${this.renderNotificationDrawerTrigger()} + + ${this.renderSettings()} - ${canAccessAdmin - ? html` - ${msg("Admin interface")} - ` - : html``} + ${this.renderAdminInterfaceLink()}
                    - ${this.me.original - ? html`  -
                    -
                    - { - return new CoreApi(DEFAULT_CONFIG) - .coreUsersImpersonateEndRetrieve() - .then(() => { - window.location.reload(); - }); - }} - > - ${msg("Stop impersonation")} - -
                    -
                    ` - : html``} + ${this.renderImpersonation()}
                    - ${userDisplay} + ${this.userDisplayName}
                    `; } + + renderApiDrawerTrigger() { + if (!this.uiConfig.enabledFeatures.apiDrawer) { + return nothing; + } + + const onClick = (ev: Event) => { + ev.stopPropagation(); + this.dispatchEvent( + new Event(EVENT_API_DRAWER_TOGGLE, { bubbles: true, composed: true }) + ); + }; + + return html`
                    + +
                    `; + } + + renderNotificationDrawerTrigger() { + if (!this.uiConfig.enabledFeatures.notificationDrawer) { + return nothing; + } + + const onClick = (ev: Event) => { + ev.stopPropagation(); + this.dispatchEvent( + new Event(EVENT_NOTIFICATION_DRAWER_TOGGLE, { bubbles: true, composed: true }) + ); + }; + + return html`
                    + +
                    `; + } + + renderSettings() { + if (!this.uiConfig.enabledFeatures.settings) { + return nothing; + } + + return html` `; + } + + renderAdminInterfaceLink() { + if (!this.canAccessAdmin) { + return nothing; + } + + return html` + ${msg("Admin interface")} + `; + } + + renderImpersonation() { + if (!this.me.original) { + return nothing; + } + + const onClick = () => { + return new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => { + window.location.reload(); + }); + }; + + return html`  +
                    +
                    + + ${msg("Stop impersonation")} + +
                    +
                    `; + } +} + +// ___ _ +// | _ )_ _ __(_)_ _ ___ ______ +// | _ \ || (_-< | ' \/ -_|_-<_-< +// |___/\_,_/__/_|_||_\___/__/__/ +// +// +@customElement("ak-interface-user") +export class UserInterface extends EnterpriseAwareInterface { + @property({ type: Boolean }) + notificationDrawerOpen = getURLParam("notificationDrawerOpen", false); + + @state() + apiDrawerOpen = getURLParam("apiDrawerOpen", false); + + ws: WebsocketClient; + + @state() + notificationsCount = 0; + + @state() + me?: SessionUser; + + oauthController: OAuthLoginController; + + constructor() { + super(); + this.ws = new WebsocketClient(); + this.fetchConfigurationDetails(); + configureSentry(true); + this.oauthController = new OAuthLoginController(this, userSettings); + } + + connectedCallback() { + super.connectedCallback(); + window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer); + window.addEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer); + window.addEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails); + } + + disconnectedCallback() { + window.removeEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, this.toggleNotificationDrawer); + window.removeEventListener(EVENT_API_DRAWER_TOGGLE, this.toggleApiDrawer); + window.removeEventListener(EVENT_WS_MESSAGE, this.fetchConfigurationDetails); + super.disconnectedCallback(); + } + + @bound + toggleNotificationDrawer() { + this.notificationDrawerOpen = !this.notificationDrawerOpen; + updateURLParams({ + notificationDrawerOpen: this.notificationDrawerOpen, + }); + } + + @bound + toggleApiDrawer() { + this.apiDrawerOpen = !this.apiDrawerOpen; + updateURLParams({ + apiDrawerOpen: this.apiDrawerOpen, + }); + } + + @bound + fetchConfigurationDetails() { + me().then((me: SessionUser) => { + this.me = me; + new EventsApi(DEFAULT_CONFIG) + .eventsNotificationsList({ + seen: false, + ordering: "-created", + pageSize: 1, + user: this.me.user.pk, + }) + .then((notifications) => { + this.notificationsCount = notifications.pagination.count; + }); + }); + } + + get isFullyConfigured() { + return !!(this.uiConfig && this.me); + } + + render() { + if (!this.isFullyConfigured) { + return nothing; + } + + return html``; + } } diff --git a/web/src/user/user-settings/UserSettingsPage.ts b/web/src/user/user-settings/UserSettingsPage.ts index 9506731728..8e016da8ee 100644 --- a/web/src/user/user-settings/UserSettingsPage.ts +++ b/web/src/user/user-settings/UserSettingsPage.ts @@ -4,11 +4,11 @@ import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/user/SessionList"; import "@goauthentik/elements/user/UserConsentList"; +import "@goauthentik/elements/user/sources/SourceSettings"; import { UserInterface } from "@goauthentik/user/UserInterface"; import "@goauthentik/user/user-settings/details/UserPassword"; import "@goauthentik/user/user-settings/details/UserSettingsFlowExecutor"; import "@goauthentik/user/user-settings/mfa/MFADevicesPage"; -import "@goauthentik/user/user-settings/sources/SourceSettings"; import "@goauthentik/user/user-settings/tokens/UserTokenList"; import { localized, msg } from "@lit/localize"; @@ -156,9 +156,14 @@ export class UserSettingsPage extends AKElement { class="pf-c-page__main-section pf-m-no-padding-mobile" >
                    -
                    - +
                    + ${msg( + "Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials.", + )}
                    + ()?.me?.user.pk)} + >
                    + ${msg("Open settings")} `; diff --git a/web/src/user/user-settings/details/stages/prompt/PromptStage.ts b/web/src/user/user-settings/details/stages/prompt/PromptStage.ts index 36237751b8..9276f8a2fe 100644 --- a/web/src/user/user-settings/details/stages/prompt/PromptStage.ts +++ b/web/src/user/user-settings/details/stages/prompt/PromptStage.ts @@ -51,10 +51,10 @@ export class UserSettingsPromptStage extends PromptStage {
                    - ${this.host.tenant?.flowUnenrollment + ${this.host.brand?.flowUnenrollment ? html` ${msg("Delete account")} ` diff --git a/web/src/user/user-settings/mfa/MFADevicesPage.ts b/web/src/user/user-settings/mfa/MFADevicesPage.ts index 5b26d152e8..86578c920e 100644 --- a/web/src/user/user-settings/mfa/MFADevicesPage.ts +++ b/web/src/user/user-settings/mfa/MFADevicesPage.ts @@ -25,6 +25,7 @@ export class MFADevicesPage extends Table { userSettings?: UserSetting[]; checkbox = true; + clearOnRefresh = true; async apiEndpoint(): Promise> { const devices = await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsAllList(); diff --git a/web/src/user/user-settings/tokens/UserTokenForm.ts b/web/src/user/user-settings/tokens/UserTokenForm.ts index 78ce3413df..d0fce872f2 100644 --- a/web/src/user/user-settings/tokens/UserTokenForm.ts +++ b/web/src/user/user-settings/tokens/UserTokenForm.ts @@ -1,4 +1,5 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { dateTimeLocal } from "@goauthentik/common/utils"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; @@ -28,6 +29,7 @@ export class UserTokenForm extends ModelForm { async send(data: Token): Promise { if (this.instance) { + data.intent = this.instance.intent; return new CoreApi(DEFAULT_CONFIG).coreTokensUpdate({ identifier: this.instance.identifier, tokenRequest: data, @@ -41,6 +43,11 @@ export class UserTokenForm extends ModelForm { } renderForm(): TemplateResult { + const now = new Date(); + const expiringDate = this.instance?.expires + ? new Date(this.instance.expires.getTime()) + : new Date(now.getTime() + 30 * 60000); + return html` { value="${ifDefined(this.instance?.description)}" class="pf-c-form-control" /> - `; + + ${this.intent == IntentEnum.AppPassword + ? html` + + ` + : html``}`; } } diff --git a/web/src/user/user-settings/tokens/UserTokenList.ts b/web/src/user/user-settings/tokens/UserTokenList.ts index 82cadeee14..9cc8e588ea 100644 --- a/web/src/user/user-settings/tokens/UserTokenList.ts +++ b/web/src/user/user-settings/tokens/UserTokenList.ts @@ -2,6 +2,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { intentToLabel } from "@goauthentik/common/labels"; import { uiConfig } from "@goauthentik/common/ui/config"; import { me } from "@goauthentik/common/users"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/ModalButton"; @@ -29,6 +30,7 @@ export class UserTokenList extends Table { expandable = true; checkbox = true; + clearOnRefresh = true; @property() order = "expires"; @@ -107,7 +109,14 @@ export class UserTokenList extends Table {
                    - ${item.expiring ? item.expires?.toLocaleString() : msg("-")} + ${item.expiring + ? html` + ${getRelativeTime(item.expires!)} + ` + : msg("-")}
                    @@ -151,7 +160,11 @@ export class UserTokenList extends Table { ${msg("Update")} ${msg("Update Token")} - + - -
                    -
                    - - -
                    - ); -} diff --git a/website/src/components/Waitlist/style.module.css b/website/src/components/Waitlist/style.module.css deleted file mode 100644 index 7bfd4a8777..0000000000 --- a/website/src/components/Waitlist/style.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.emailInput { - width: 100%; - line-height: 2rem; - margin-bottom: 2rem; - padding: 0.5rem 1rem; -} diff --git a/website/src/css/custom.css b/website/src/css/custom.css index aec8cb4ef3..beb7ca2f28 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -37,24 +37,6 @@ stroke: var(--white); } -.hero--primary { - background: radial-gradient( - circle, - rgba(47, 6, 75, 1) 0%, - var(--ifm-color-primary) 50% - ); - padding-bottom: 5.3rem !important; - /* fix aliasing at the edge */ - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 3vw)); - clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 3vw)); -} - -.before-after-slider img { - max-width: none; -} - .header-github-link:hover { opacity: 0.6; } diff --git a/website/src/footer.html b/website/src/footer.html deleted file mode 100644 index 7a75dac1cc..0000000000 --- a/website/src/footer.html +++ /dev/null @@ -1,109 +0,0 @@ - - - -
                    -
                    -
                    -
                    - * indicates required -
                    -
                    - - - -
                    -
                    - - -
                    - - -
                    -
                    -
                    -
                    - -
                    - - - diff --git a/website/src/pages/api/index.jsx b/website/src/pages/api/index.jsx deleted file mode 100644 index 8e6647dc04..0000000000 --- a/website/src/pages/api/index.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import Layout from "@theme/Layout"; -import Head from "@docusaurus/Head"; -import BrowserOnly from "@docusaurus/core/lib/client/exports/BrowserOnly"; - -function APIPage() { - return ( - - - - - - {() => { - window.location.pathname = "/developer-docs/api/"; - }} - - - ); -} -export default APIPage; diff --git a/website/src/pages/api/v3.jsx b/website/src/pages/api/v3.jsx deleted file mode 100644 index 7cdd45289e..0000000000 --- a/website/src/pages/api/v3.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import Layout from "@theme/Layout"; -import Head from "@docusaurus/Head"; -import BrowserOnly from "@docusaurus/core/lib/client/exports/BrowserOnly"; - -function APIPage() { - return ( - - - - - - {() => { - window.location.pathname = "/developer-docs/api/"; - }} - - - ); -} -export default APIPage; diff --git a/website/src/pages/index.jsx b/website/src/pages/index.jsx deleted file mode 100644 index feea2b2920..0000000000 --- a/website/src/pages/index.jsx +++ /dev/null @@ -1,267 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import Layout from "@theme/Layout"; -import Link from "@docusaurus/Link"; -import Head from "@docusaurus/Head"; -import BrowserOnly from "@docusaurus/core/lib/client/exports/BrowserOnly"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -import useBaseUrl from "@docusaurus/useBaseUrl"; -import styles from "./styles.module.css"; -import Comparison from "../components/Comparison"; -import "react-before-after-slider-component/dist/build.css"; -import { NewsBar } from "../components/NewsBar"; -import { TextSlide } from "../components/TextSlide"; - -function Feature({ imageUrl, title, description }) { - const imgUrl = useBaseUrl(imageUrl); - return ( -
                    - {imgUrl && ( -
                    - {title} -
                    - )} -

                    {title}

                    -

                    {description}

                    -
                    - ); -} - -function Home() { - const context = useDocusaurusContext(); - const { siteConfig = {} } = context; - return ( - - - - -
                    -
                    -
                    -
                    -

                    -
                    Replace
                    - -
                    with a unified platform.
                    -

                    -

                    - Unify all of your identity needs into a single - platform -

                    -
                    - - Get Started - - - Reasons to switch - -
                    -
                    -
                    - authentik logo -
                    -
                    -
                    -
                    -
                    -
                    -
                    -
                    - - Identity made easy. authentik makes - single-sign on (SSO), user enrollment, - and access control simple. - - } - /> - - authentik lets you build your workflow - as you need it, no limitations. - - } - /> - - Implement custom verification or access - control logic using Python code. - - } - /> -
                    -
                    -
                    -
                    - -
                    -
                    -
                    -
                    -
                    - - {() => { - const ReactBeforeSliderComponent = require("react-before-after-slider-component"); - return ( - - ); - }} - -
                    -
                    -

                    What is authentik?

                    -

                    - authentik is an open-source Identity - Provider focused on flexibility and - versatility. You can use authentik in an - existing environment to add support for new - protocols, implement sign-up/recovery/etc. - in your application so you don't have to - deal with it, and many other things. -

                    -
                    -
                    -
                    -
                    -

                    Utmost flexibility

                    -

                    - You can adopt authentik to your environment, - regardless of your requirements. Need an - Active-Directory integrated SSO Provider? Do - you want to implement a custom enrollment - process for your customers? Are you - developing an application and don't want to - deal with User verification and recovery? - authentik can do all of that, and more! -

                    -
                    -
                    - - {() => { - const ReactBeforeSliderComponent = require("react-before-after-slider-component"); - return ( - - ); - }} - -
                    -
                    -
                    -
                    -
                    -
                    - -
                    -
                    -
                    -
                    -

                    Try authentik now!

                    -
                    - - Get Started - - - Learn about enterprise - -
                    -
                    -
                    -
                    -
                    - ); -} - -export default Home; diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx new file mode 100644 index 0000000000..dba1b8dfa1 --- /dev/null +++ b/website/src/pages/index.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import clsx from "clsx"; +import Layout from "@theme/Layout"; +import BrowserOnly from "@docusaurus/BrowserOnly"; + +function Home() { + return ( + + + {() => { + window.location.href = "/docs"; + }} + +
                    +
                    +

                    authentik Documentation

                    +
                    +
                    +
                    + ); +} + +export default Home; diff --git a/website/src/pages/jobs/index.jsx b/website/src/pages/jobs/index.jsx deleted file mode 100644 index 1f4138f1ec..0000000000 --- a/website/src/pages/jobs/index.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import Layout from "@theme/Layout"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -import { useColorMode } from "@docusaurus/theme-common"; -import { useEffect } from "react"; - -function JobBoardWrapper() { - const context = useDocusaurusContext(); - const { siteConfig = {} } = context; - return ( - - - - ); -} - -function JobBoard() { - const { colorMode, setColorMode } = useColorMode(); - if (colorMode !== "light") { - setColorMode("light"); - } - useEffect(() => { - Grnhse.Iframe.load(); - }, []); - return
                    ; -} - -export default JobBoardWrapper; diff --git a/website/src/pages/legal/terms.md b/website/src/pages/legal/terms.md deleted file mode 100644 index 34e3802f5d..0000000000 --- a/website/src/pages/legal/terms.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Authentik Subscription Terms ---- - -# Authentik Subscription Terms - -By signing this agreement, you and any entity that you represent ("Customer") are unconditionally consenting to be bound by and are becoming a party to these Authentik Subscription Terms ("Agreement") as of the date of Customer's first download of the Licensed Materials (the "Effective Date"). - -Customer's continued use of the software or any Licensed Materials provided by Authentik Security Inc. (a Delaware corporation with its principal offices located on Market St in San Francisco CA) or one of its affiliates and/or subsidiaries, as specified on an order form or quote ("Authentik") shall also constitute assent to the terms of this agreement. - -If these terms are considered an offer, acceptance is expressly limited to these terms. If you are executing this agreement on behalf of an organization, you represent that you have the authority to do so. - -## 1. LICENSE AND SUPPORT - -1.1 Subject to the terms and conditions of this Agreement, Authentik hereby grants to Customer and its Affiliates (as defined below) a limited, non-exclusive, non-transferable, non-sublicensable license for Customer’s and its Affiliates’ employees and contractors to (1) internally (a) use, reproduce, modify, prepare derivative works based upon, and display the code of Authentik Enterprise Edition ("Authentik EE") at the tier level selected by Customer or set forth on a Quote (as defined below), if applicable with the specifications generally promulgated by Authentik from time to time (the “Software”) solely (i) for its internal use in connection with the development of Customer’s and/or its Affiliates’ own software, and (ii) by the number of Hosts (defined below) for which Customer has paid Authentik; and (b) use the documentation, training materials or other materials supplied by Authentik (the “Other Authentik Materials”); and (2) modify the Software and publish patches to the Software. - -Notwithstanding anything to the contrary, Customer agrees that Authentik and/or its licensors (as applicable) retain all right, title and interest in and to all Software incorporated in such modifications and/or patches, and all such Software may only be used, copied, modified, displayed, distributed, or otherwise exploited in full compliance with this Agreement, and with a valid Authentik Enterprise Edition subscription for the correct number of Hosts. - -The Software and Other Authentik Materials are collectively referred to herein as the “Licensed Materials.” - -“Affiliate” means any entity(ies) controlling, controlled by, and/or under common control with a party hereto, where “control” means the ownership of more than 50% of the voting securities in such entity. - -"User" means each individual end-user (person or machine) of Customer and/or its Affiliates (including, without limitation, employees, agents or consultants thereof) with access to the Licensed Materials hereunder. - -"Host" means each individual machine (real or virtual, including servers, containers, workstations, smartphones, POS, industrial controls, gateways, sensors, IoT endpoints, or any other physical or simulated computing interface or machine) of Customer and/or its Affiliates (including, without limitation, employees, agents or consultants thereof) with access to Licensed Materials hereunder. - -1.2 Subject to the terms hereof, Authentik will provide reasonable support to Customer for the Licensed Materials as set forth at https://goauthentik.io, for the support plan selected and paid for by Customer. Notwithstanding anything to the contrary, in the event that Customer does not reasonably comply with written specifications or instructions from Authentik’s service engineers regarding any support issue or request (including without limitation, failure to make backups of Customer’s Licensed Materials) (each, a “Support Issue”), Authentik may terminate its support obligations to Customer with respect to such Support Issue upon fifteen (15) days’ written notice if Customer does not cure such noncompliance within the notice period. - -1.2.1 Authentik will use reasonable commercial efforts to respond to support questions by phone or email during the next business day. The number of support questions is not limited. - -## 2. RESTRICTIONS AND RESPONSIBILITIES - -2.1 Except as expressly authorized in Section 1.1, Customer will not, and will not permit any third party to: use the Licensed Materials for any purpose other than as specifically authorized in Section 1, or in such a manner that would enable any unlicensed person to access the Licensed Materials; use the Licensed Materials or any other Authentik software for timesharing or service bureau purposes or for any purpose other than its and its Affiliates’ own internal use (including without limitation, sublicensing, distributing, selling, reselling any of the foregoing); except as expressly permitted herein; use the Licensed Materials in connection with any high risk or strict liability activity (including, without limitation, space travel, firefighting, police operations, power plant operation, military operations, rescue operations, hospital and medical operations or the like); use the Licensed Materials or software other than in accordance with this Agreement and in compliance with all applicable laws and regulations (including but not limited to any privacy laws, and laws and regulations concerning intellectual property, consumer and child protection, obscenity or defamation); or use the Licensed Materials in any manner that (1) is harmful, fraudulent, deceptive, threatening, abusive, harassing, tortious, defamatory, vulgar, obscene, or libelous (including without limitation, accessing any computer, computer system, network, software, or data without authorization, breaching the security of another user or system, and/or attempting to circumvent any Host or User authentication or security process), (2) impersonates any person or entity, including without limitation any employee or representative of Authentik, or (3) contains a virus, trojan horse, worm, time bomb, unsolicited bulk, commercial, or “spam” message, or other harmful computer code, file, or program (including without limitation, password guessing programs, decoders, password gatherers, keystroke loggers, cracking tools, packet sniffers, and/or encryption circumvention programs). - -2.2 Customer will cooperate with Authentik in connection with the performance of this Agreement by making available such personnel and information as may be reasonably required, and taking such other actions as Authentik may reasonably request. Customer will also cooperate with Authentik in establishing a password or other procedures for verifying that only designated employees of Customer have access to any administrative functions of the Licensed Materials. Customer shall maintain during the term of this Agreement and through the end of the third year after the date on which the final payment is made under this Agreement, books, records, contracts and accounts relating to the payments due Authentik under this Agreement (collectively, the “Customer Records”). Authentik may, at its sole expense, upon 30 days’ prior written notice to Customer and during Customer’s normal business hours and subject to industry-standard confidentiality obligations, hire an independent third party auditor to audit the Customer Records only to verify the amounts payable under this Agreement. If an audit reveals underpayment, then Customer shall promptly pay the deficiency to Authentik plus late fees pursuant to Section 5.2. Authentik shall bear the cost of an audit unless the audit reveals underpayment by more than 5% for the audited period, in which case Customer shall promptly pay Authentik for the reasonable costs of the audit. - -2.3 Customer will be responsible for maintaining the security of Customer’s account, passwords (including but not limited to administrative and User passwords and credentials for Hosts like e) and files, and for all uses of Customer account with or without Customer’s knowledge or consent. - -## 3. CONFIDENTIALITY - -3.1 Each party (the “Receiving Party”) understands that the other party (the “Disclosing Party”) has disclosed or may disclose information relating to the Disclosing Party’s technology or business (hereinafter referred to as “Proprietary Information” of the Disclosing Party). Without limiting the foregoing, the Licensed Materials are Authentik Proprietary Information. - -3.2 The Receiving Party agrees: (i) not to divulge to any third person any such Proprietary Information, (ii) to give access to such Proprietary Information solely to those employees with a need to have access thereto for purposes of this Agreement, and (iii) to take the same security precautions to protect against disclosure or unauthorized use of such Proprietary Information that the party takes with its own proprietary information, but in no event will a party apply less than reasonable precautions to protect such Proprietary Information. The Disclosing Party agrees that the foregoing will not apply with respect to any information that the Receiving Party can document (a) is or becomes generally available to the public without any action by, or involvement of, the Receiving Party, or (b) was in its possession or known by it prior to receipt from the Disclosing Party, or (c) was rightfully disclosed to it without restriction by a third party, or (d) was independently developed without use of any Proprietary Information of the Disclosing Party. Nothing in this Agreement will prevent the Receiving Party from disclosing Proprietary Information pursuant to any judicial or governmental order, provided that the Receiving Party gives the Disclosing Party reasonable prior notice of such disclosure to contest such order. In any event, Authentik may collect data with respect to and report on the aggregate response rate and other aggregate measures of the Licensed Materials’ performance and Customer’s usage of the Licensed Materials; provided that Authentik will not identify Customer as the source of any such data without Customer’s prior written consent. For the avoidance of doubt, use of a third party to host the data collected shall not be deemed a disclosure. - -3.3 Each party acknowledges and agrees that the other may suffer irreparable damage in the event of a breach of the terms of Sections 1.1, 2.1 or 3.2 of this Agreement and that such party will be entitled to seek injunctive relief (without the necessity of posting a bond) in the event of any such breach. - -3.4 Both parties will have the right to disclose the existence of the relationship between the parties, but not the terms and conditions of this Agreement, unless such disclosure of the Agreement terms is approved in writing by both Parties prior to such disclosure, or is included in a filing required to be made by a party with a governmental authority (provided such party will use reasonable efforts to obtain confidential treatment or a protective order) or is made on a confidential basis as reasonably necessary to potential investors or acquirers. - -## 4. INTELLECTUAL PROPERTY RIGHTS - -4.1 Except as expressly set forth herein, Authentik alone (and its licensors, where applicable) will retain all intellectual property rights relating to the Licensed Materials and any suggestions, ideas, enhancement requests, feedback, code, or other recommendations provided by Customer, its Affiliates or any third party relating to the Licensed Materials, which are hereby assigned to Authentik. This Agreement is not a sale and does not convey to Customer any rights of ownership in or related to the Licensed Materials, or any intellectual property rights. - -4.2 Customer shall not remove, alter or obscure any of Authentik’s (or its licensors’) copyright notices, proprietary legends, trademark or service mark attributions, patent markings or other indicia of Authentik’s (or its licensors’) ownership or contribution from the Licensed Materials. Additionally, Customer agrees to reproduce and include Authentik’s (and its licensors’) proprietary and copyright notices on any copies of the Licensed Materials, or on any portion thereof, including reproduction of the copyright notice. Notwithstanding anything to the contrary herein, certain components of the Licensed Materials, including without limitation, any component of the Licensed Materials distributed by Authentik as part of the Authentik Community Edition, are licensed by third parties pursuant to the terms of certain third party licenses described in such source code annotations. - -4.3 Customer and its licensors shall (and Customer hereby represents and warrants that they do) have and retain all right, title and interest (including, without limitation, sole ownership of) all software, information, content and data provided by or on behalf of Customer or made available or otherwise distributed through use of the Licensed Materials (“Content”) and the intellectual property rights with respect to that Content. If Authentik receives any notice or claim that any Content, or Customer’s activities hereunder (including without limitation, with respect to any Content), infringes or violates the rights of a third party or any applicable law or regulation (a “Claim”), Customer will indemnify, defend and hold Authentik harmless from all liability, damages, settlements, attorney fees and other costs and expenses in connection with any such Claim, as incurred. The immediately foregoing indemnity obligations are expressly conditioned on Authentik providing Customer with prompt notice of, and reasonable cooperation and sole control over the defense and/or settlement of the applicable Claim. Subject to the foregoing, Authentik may participate in the defense and/or settlement of any applicable Claim with counsel of its choosing at its own expense. - -4.4 Authentik will defend, indemnify and hold Customer harmless from liability and other amounts paid or payable to unaffiliated third parties resulting from (i) the infringement or violation of any intellectual property or proprietary rights by the Licensed Materials or (ii) the violation of applicable law or regulation by Authentik in performance of its obligations hereunder, provided Authentik is promptly notified of any and all threats, claims and proceedings related thereto and given reasonable assistance and the opportunity to assume sole control over defense and settlement thereof. Subject to the foregoing, Customer may participate in the defense and/or settlement of any claim that is indemnifiable by Authentik with counsel of its choosing at its own expense. The foregoing obligations do not apply with respect to portions or components of the Licensed Materials (i) not created by Authentik, (ii) that are modified after delivery by Authentik, (iii) combined with other products, processes or materials where the alleged infringement relates to such combination, (iv) where Customer continues allegedly infringing activity after being notified thereof or after being informed of modifications that would have avoided the alleged infringement, or (v) where Customer’s use of the Licensed Materials is not strictly in accordance with this Agreement and all related documentation. - -## 5. PAYMENT OF FEES - -5.1 Unless and until Authentik and Customer have executed a quote document specifically referencing this Agreement with respect to amounts due on account of the Licensed Materials (a “Quote”, which is hereby incorporated by reference, if applicable), and unless Customer’s subscription to (and payment with respect to) the Licensed Materials has been made on Customer’s behalf by a reseller, Customer will pay Authentik the applicable fees as set forth at https://goauthentik.io (the “Pricing”) for the Licensed Materials selected and/or used by Customer (the “Fees”) without any right of set-off or deduction. On each anniversary of the Effective Date, Authentik will invoice Customer (or its reseller, if applicable) with respect to any and all additional Customer Hosts of the Licensed Materials beyond those for whom Customer has pre-paid, as of such date (and for whom the Fees due pursuant to such invoice will be the then-current per-year Host fee with respect to the year just ended, and the then current per-year Host fee with respect to all subsequent years, unless otherwise agreed in writing by both parties (collectively, a “True-Up”)). For Customers that have pre-paid all Fees for multi-year subscriptions for Licensed Materials pursuant to a Quote, on each anniversary of the Effective Date during the term of this Agreement, (i) a new license key will be provided, and (ii) a True-Up will be conducted. All additional Hosts purchased shall be co-terminated through the end of the original Subscription period. - -5.2 All payments will be made in accordance with the payment schedule and the method of payment set forth in the Pricing. If not otherwise specified, (a) Authentik will invoice Customer with respect to Fees up-front, for each term of the Customer's subscription (as applicable), and (b) payments will be due within thirty (30) days of Customer's receipt of correct invoice. Except as expressly set forth in this Agreement, all Fees paid and/or due hereunder (including any prepaid amounts) are non-refundable, including without limitation if this Agreement is terminated in accordance with Section 6 below. If Customer terminates this Agreement pursuant to Section 6.2 within 45 calendar days from receipt of the initial invoice for the Licensed Materials, Authentik will refund all Fees paid hereunder. - -5.3 Any unpaid fees are subject to a finance charge of one percent (1.0%) per month, or the maximum permitted by law, whichever is lower, plus all expenses of collection, including reasonable attorneys’ fees. Fees under this Agreement are exclusive of all taxes, including national, state or provincial and local use, sales, value-added, property and similar taxes, if any. Customer agrees to pay such taxes (excluding US taxes based on Authentik's net income) unless Customer has provided Authentik with a valid exemption certificate. In the case of any withholding requirements, Customer will pay any required withholding itself and will not reduce the amount paid to Authentik on account thereof. - -## 6. TERMINATION - -6.1 This Agreement shall continue until terminated in accordance with this Section 6. Either party may terminate this Agreement upon 15 days’ written notice to the other party hereto in the event that Customer has no then-current subscription and license key with respect to the Licensed Materials. - -6.2 Customer may terminate this Agreement at any time upon written notice to Authentik. Either party may terminate this Agreement immediately upon 15 days’ written notice to the other party in the event of any material breach of this Agreement (including without limitation, any breach of Section 2.2 and/or failure to pay any amounts when due hereunder) by such party where such material breach is not cured during such notice period. - -6.3 Either party may terminate this Agreement, without notice, (i) upon the institution by or against the other party of insolvency, receivership or bankruptcy proceedings (provided such proceedings are not dismissed within one hundred twenty (120) days of such institution), (ii) upon the other party's making an assignment for the benefit of creditors, or (iii) upon the other party's dissolution or ceasing to do business without a successor. - -6.4 Customer’s rights to the Licensed Materials, and any licenses granted hereunder, shall terminate upon any termination of this Agreement. In the event that Customer terminates this Agreement pursuant to the second sentence of Section 6.2 above, Authentik will refund to Customer a pro-rated portion of pre-paid Fees for Services not actually received by Customer as of the date of such termination. The following Sections will survive any termination of this Agreement: 2 through 6 (except for Section 4.3), and 8 through 11. - -## 7. WARRANTY; CUSTOMER SOFTWARE SECURITY - -Authentik represents and warrants that (i) it has all rights and licenses necessary for it to perform its obligations hereunder, and (ii) it will not knowingly include, in any Authentik software released to the public and provided to Customer hereunder, any computer code or other computer instructions, devices or techniques, including without limitation those known as disabling devices, trojans, or time bombs, that are intentionally designed to disrupt, disable, harm, infect, defraud, damage, or otherwise impede in any manner, the operation of a network, computer program or computer system or any component thereof, including its security or user data. If, at any time, Authentik fails to comply with the warranty in this Section, Customer may promptly notify Authentik in writing of any such noncompliance. Authentik will, within thirty (30) days of receipt of such written notification, either correct the noncompliance or provide Customer with a plan for correcting the noncompliance. If the noncompliance is not corrected or if a reasonably acceptable plan for correcting them is not established during such period, Customer may terminate this Agreement as its sole and exclusive remedy for such noncompliance. - -## 8. WARRANTY DISCLAIMER - -EXCEPT AS EXPRESSLY STATED HEREIN, THE LICENSED MATERIALS, SOFTWARE AND AUTHENTIK PROPRIETARY INFORMATION AND ANYTHING PROVIDED IN CONNECTION WITH THIS AGREEMENT ARE PROVIDED "AS-IS," WITHOUT ANY WARRANTIES OF ANY KIND. AUTHENTIK AND ITS LICENSORS HEREBY DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT. - -## 9. LIMITATION OF LIABILITY - -EXCEPT WITH RESPECT TO BREACH(ES) OF SECTION 1.1 AND/OR 2.1, IN NO EVENT WILL EITHER PARTY OR THEIR LICENSORS BE LIABLE FOR ANY INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OF THE LICENSED MATERIALS OR ANYTHING PROVIDED IN CONNECTION WITH THIS AGREEMENT, ANY DELAY OR INABILITY TO USE THE LICENSED MATERIALS OR ANYTHING PROVIDED IN CONNECTION WITH THIS AGREEMENT OR OTHERWISE ARISING FROM THIS AGREEMENT, INCLUDING WITHOUT LIMITATION, LOSS OF REVENUE OR ANTICIPATED PROFITS OR LOST BUSINESS OR LOST SALES, WHETHER BASED IN CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR OTHERWISE, EVEN IF SUCH PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF DAMAGES. EXCEPT WITH RESPECT TO BREACH(ES) OF SECTION 1.1 AND/OR 2.1, THE TOTAL LIABILITY OF EACH PARTY AND ITS LICENSORS, WHETHER BASED IN CONTRACT, TORT (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, WILL NOT EXCEED, IN THE AGGREGATE, THE GREATER OF (i) ONE THOUSAND DOLLARS ($1,000), OR (ii) THE FEES PAID TO AUTHENTIK HEREUNDER IN ONE YEAR PERIOD ENDING ON THE DATE THAT A CLAIM OR DEMAND IS FIRST ASSERTED. THE FOREGOING LIMITATIONS WILL APPLY NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY LIMITED REMEDY. - -## 10. U.S. GOVERNMENT MATTERS - -Notwithstanding anything else, Customer may not provide to any person or export or re-export or allow the export or re-export of the Licensed Materials or any software or anything related thereto or any direct product thereof (collectively “Controlled Subject Matter”), in violation of any restrictions, laws or regulations of the United States Department of Commerce, the United States Department of Treasury Office of Foreign Assets Control, or any other United States or foreign agency or authority. Without limiting the foregoing Customer acknowledges and agrees that the Controlled Subject Matter will not be used or transferred or otherwise exported or re-exported to countries as to which the United States maintains an embargo (collectively, “Embargoed Countries”), or to or by a national or resident thereof, or any person or entity on the U.S. Department of Treasury’s List of Specially Designated Nationals or the U.S. Department of Commerce’s Table of Denial Orders (collectively, “Designated Nationals”). The lists of Embargoed Countries and Designated Nationals are subject to change without notice. Use of the Licensed Materials is representation and warranty that neither the User nor Host is located in, under the control of, or a national or resident of an Embargoed Country or Designated National. The Controlled Subject Matter may use or include encryption technology that is subject to licensing requirements under the U.S. Export Administration Regulations. As defined in FAR section 2.101, any software and documentation provided by Authentik are “commercial items” and according to DFAR section 252.2277014(a)(1) and (5) are deemed to be “commercial computer software” and “commercial computer software documentation.” Consistent with DFAR section 227.7202 and FAR section 12.212, any use modification, reproduction, release, performance, display, or disclosure of such commercial software or commercial software documentation by the U.S. Government will be governed solely by the terms of this Agreement and will be prohibited except to the extent expressly permitted by the terms of this Agreement. - -## 11. MISCELLANEOUS - -If any provision of this Agreement is found to be unenforceable or invalid, that provision will be limited or eliminated to the minimum extent necessary so that this Agreement will otherwise remain in full force and effect and enforceable. This Agreement is not assignable, transferable or sublicensable by either party without the other party’s prior written consent, not to be unreasonably withheld or delayed; provided that either party may transfer and/or assign this Agreement to a successor in the event of a sale of all or substantially all of its business or assets to which this Agreement relates. Both parties agree that this Agreement is the complete and exclusive statement of the mutual understanding of the parties and supersedes and cancels all previous written and oral agreements, communications and other understandings relating to the subject matter of this Agreement, and that all waivers and modifications must be in a writing signed or otherwise agreed to by each party, except as otherwise provided herein. No agency, partnership, joint venture, or employment is created as a result of this Agreement and neither party has any authority of any kind to bind the other in any respect whatsoever. In any action or proceeding to enforce rights under this Agreement, the prevailing party will be entitled to recover costs and attorneys’ fees. All notices under this Agreement will be in writing and will be deemed to have been duly given when received, if personally delivered; when receipt is electronically confirmed, if transmitted by facsimile or e-mail; and upon receipt, if sent by certified or registered mail (return receipt requested), postage prepaid. Authentik will not be liable for any loss resulting from a cause over which it does not have direct control. This Agreement will be governed by the laws of the State of California, U.S.A. without regard to its conflict of laws provisions. The federal and state courts sitting in San Francisco County, California, U.S.A. will have proper and exclusive jurisdiction and venue with respect to any disputes arising from or related to the subject matter of this Agreement. - -## 12. DATA PRIVACY - -Customer shall ensure that any and all information or data, including without limitation, personal data, used by Customer in connection with the Agreement (“Customer Data”) is collected, processed, transferred and used in full compliance with Applicable Data Protection Laws (as defined below) and that it has all obtained all necessary authorizations and consents from any data subjects to process Customer Data. Customer shall adopt and maintain appropriate organizational, technical and security measures prior to any such collection, processing or transfer in order to protect against unauthorized access to or use of Customer Data. Customer shall immediately inform Authentik upon becoming aware of any breach within the meaning of Applicable Data Protection Law relating to Customer Data (a “Security Incident”) and to cooperate with Authentik in any investigation thereof and in the implementation of any measures reasonably required to be taken in response thereto. If required by Applicable Data Protection Laws, the parties will enter into standard contractual clauses under GDPR (as defined below) for the transfer of any Customer Data outside of the European Union. For purposes hereof: (a) “Applicable Data Protection Laws” means any applicable laws, statutes or regulations as may be amended, extended or re-enacted from time to time which relate to personal data including without limitation (i) prior to 25 May 2018, the EU Data Protection Directive 95/46/EC as transposed into EU Member State law; (ii) from and after 25 May 2018, GDPR and any EU Member State laws implementing the GDPR; and (iii) the e-Privacy Directive 2002/58/EC, as amended and as transposed into EU Member State law and any legislation replacing the e-Privacy Directive and (b) “GDPR” means the Regulation (EU) 2016/679 of the European Parliament and of the Counsel of 27 April 2016 on the protection of natural persons with regard to the processing of personal data and on the free movement of such data, - -[THE REMAINDER OF THIS PAGE INTENTIONALLY LEFT BLANK] - -**IN WITNESS WHEREOF,** the parties have executed this Agreement as of the Effective Date. - -_This work, "authentik Subscription Terms", is a derivative of "[Fleet Subscription Terms](https://fleetdm.com/legal/terms)", by [Fleet Device Management Inc.](https://fleetdm.com/handbook/company), used under CC BY-SA 4.0. "authentik Subscription Terms" is licensed under CC BY-SA 4.0 by Authentik Security Inc._ - -[![License: CC BY-SA 4.0](https://licensebuttons.net/l/by-sa/4.0/80x15.png)](https://creativecommons.org/licenses/by-sa/4.0/) diff --git a/website/src/pages/pricing/index.jsx b/website/src/pages/pricing/index.jsx deleted file mode 100644 index 37b7dfc7ff..0000000000 --- a/website/src/pages/pricing/index.jsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from "react"; -import Layout from "@theme/Layout"; -import Link from "@docusaurus/Link"; -import Card from "../../components/PricingQuestions/Card"; -import useBaseUrl from "@docusaurus/useBaseUrl"; - -export default function pricingPage() { - return ( - -
                    -
                    -

                    Pricing

                    - -
                    -
                    -
                    -
                    -

                    Open Source

                    -
                    -
                    -
                  • Open source
                  • -
                  • Self-hosted
                  • -
                  • Supports OAuth2/OpenID Connect
                  • -
                  • Supports SAML
                  • -
                  • Supports LDAP
                  • -
                  • Supports SCIM
                  • -
                  • Supports Proxy authentication
                  • -
                  • Advanced policy engine
                  • -
                    -
                    -

                    Free, forever

                    - - Get Started - -
                    -
                    -
                    -
                    -
                    -
                    -

                    Enterprise Self-Hosted

                    -
                    -
                    -
                  • Self-hosted
                  • -
                  • Source-available
                  • -
                  • Long-term-support releases
                  • -
                  • Enterprise support plan
                  • -
                  • Web-based RDP/SSH access (planned)
                  • -
                  • Push-notification MFA (planned)
                  • -
                  • Desktop authentication (planned)
                  • -
                  • AI-based risk assessment (planned)
                  • -
                    -
                    -

                    - $5 /internal user/month -

                    -

                    - $0.02{" "} - /external user/month -

                    - - Get Started - -
                    -
                    -
                    -
                    -
                    -
                    -

                    Enterprise Cloud

                    -
                    -
                    -
                  • Hosted and Managed by authentik
                  • -
                  • - Easily shift to self-hosted if needed -
                  • -
                  • Source-available
                  • -
                  • Long-term-support releases
                  • -
                  • Enterprise support plan
                  • -
                  • Web-based RDP/SSH access (planned)
                  • -
                  • Push-notification MFA (planned)
                  • -
                  • Desktop authentication (planned)
                  • -
                  • AI-based risk assessment (planned)
                  • -
                    -
                    -

                    Minimum 100 users

                    -

                    - $5 /internal user/month -

                    -

                    - $0.02{" "} - /external user/month -

                    - - Join waitlist - -
                    -
                    -
                    -
                    -
                    -
                    - -
                    -
                    -
                    -
                    -

                    - Frequently Asked Questions -

                    -
                    - - - - - -
                    -
                    -
                    -
                    -
                    -
                    - ); -} diff --git a/website/src/pages/pricing/waitlist/cloud.jsx b/website/src/pages/pricing/waitlist/cloud.jsx deleted file mode 100644 index 00ffcb3307..0000000000 --- a/website/src/pages/pricing/waitlist/cloud.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; -import { WaitListForm } from "../../../components/Waitlist"; -import Layout from "@theme/Layout"; - -export default function waitlistCloud() { - return ( - - - - ); -} diff --git a/website/src/pages/styles.module.css b/website/src/pages/styles.module.css deleted file mode 100644 index be7c9171a4..0000000000 --- a/website/src/pages/styles.module.css +++ /dev/null @@ -1,98 +0,0 @@ -/* stylelint-disable docusaurus/copyright-header */ -.heroBanner { - padding: 4rem 0; - text-align: center; - position: relative; - overflow: hidden; - overflow-wrap: normal; - color: var(--white); -} - -.headerRow { - margin-bottom: -10rem; -} - -.heroImage { - flex-direction: column; - justify-items: center; -} - -.heroImage > img { - filter: drop-shadow(5px 5px 25px rgba(0, 0, 0, 0.6)); -} - -@media (max-width: 996px) { - .headerRow { - margin-bottom: 0; - } - .hiddenOnMobile { - display: none; - } - .heroBanner { - padding: 2rem; - } -} - -.ak_hero__title { - font-size: 1.75rem; -} - -@media (min-width: 388px) { - .ak_hero__title { - font-size: 2rem; - } -} - -@media (min-width: 520px) { - .ak_hero__title { - font-size: 3rem; - } -} - -.buttons { - display: flex; - align-items: center; - justify-content: center; - align-items: center; - flex-wrap: wrap; -} - -.buttons > * { - margin: 0.25rem; -} - -.features { - display: flex; - align-items: center; - padding: 2rem 0; - width: 100%; - flex-direction: column; -} - -.row { - padding-bottom: 2rem; -} - -.rowFullWidth { - width: 100%; - padding: 1rem 3rem; -} - -.rowDark { - background-color: var(--ifm-color-secondary-contrast-foreground); - color: var(--ifm-color-secondary-contrast-background); -} - -.rowAuthentik { - color: var(--white); - background-color: var(--ifm-color-primary); -} - -.newsBar { - margin-bottom: 4rem; -} - -.footerCTA { - padding: 3rem; - text-align: center; -} diff --git a/website/src/pages/terraform-provider.jsx b/website/src/pages/terraform-provider.jsx deleted file mode 100644 index 47bfa52f1b..0000000000 --- a/website/src/pages/terraform-provider.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; -import Layout from "@theme/Layout"; -import Head from "@docusaurus/Head"; -import BrowserOnly from "@docusaurus/core/lib/client/exports/BrowserOnly"; - -function TerraformProviderPage() { - return ( - - - - - - {() => { - window.location.assign( - "https://registry.terraform.io/providers/goauthentik/authentik/latest/docs", - ); - }} - - - ); -} -export default TerraformProviderPage; diff --git a/website/src/theme/BlogPostPage/index.js b/website/src/theme/BlogPostPage/index.js deleted file mode 100644 index e232b0d925..0000000000 --- a/website/src/theme/BlogPostPage/index.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import { - HtmlClassNameProvider, - ThemeClassNames, -} from "@docusaurus/theme-common"; -import { - BlogPostProvider, - useBlogPost, -} from "@docusaurus/theme-common/internal"; -import BlogLayout from "@theme/BlogLayout"; -import BlogPostItem from "@theme/BlogPostItem"; -import BlogPostPaginator from "@theme/BlogPostPaginator"; -import BlogPostPageMetadata from "@theme/BlogPostPage/Metadata"; -import TOC from "@theme/TOC"; -import { DiscussionEmbed } from "disqus-react"; - -function BlogPostPageContent({ sidebar, children }) { - const { metadata, toc } = useBlogPost(); - const { nextItem, prevItem, frontMatter } = metadata; - const { - hide_table_of_contents: hideTableOfContents, - toc_min_heading_level: tocMinHeadingLevel, - toc_max_heading_level: tocMaxHeadingLevel, - } = frontMatter; - return ( - 0 ? ( - - ) : undefined - } - > - {children} - - {(nextItem || prevItem) && ( - - )} - - ); -} -export default function BlogPostPage(props) { - const BlogPostContent = props.content; - const title = props.content.frontMatter.title.substring(0, 200); - const fmtId = title.replace(/^\//, "").replaceAll(/[\s\/]/gi, "-"); - const disqusId = fmtId == "" ? "main" : fmtId; - return ( - - - - - - - - - - - ); -} diff --git a/website/static/img/landing_login_card.png b/website/static/img/landing_login_card.png deleted file mode 100644 index b070b60744..0000000000 Binary files a/website/static/img/landing_login_card.png and /dev/null differ diff --git a/website/static/img/landing_screen_admin_dark.jpg b/website/static/img/landing_screen_admin_dark.jpg deleted file mode 100644 index f0b8099834..0000000000 Binary files a/website/static/img/landing_screen_admin_dark.jpg and /dev/null differ diff --git a/website/static/img/landing_screen_admin_light.jpg b/website/static/img/landing_screen_admin_light.jpg deleted file mode 100644 index 209d000697..0000000000 Binary files a/website/static/img/landing_screen_admin_light.jpg and /dev/null differ diff --git a/website/static/img/landing_screen_apps_dark.jpg b/website/static/img/landing_screen_apps_dark.jpg deleted file mode 100644 index 86715724af..0000000000 Binary files a/website/static/img/landing_screen_apps_dark.jpg and /dev/null differ diff --git a/website/static/img/landing_screen_apps_light.jpg b/website/static/img/landing_screen_apps_light.jpg deleted file mode 100644 index a2fe6feba5..0000000000 Binary files a/website/static/img/landing_screen_apps_light.jpg and /dev/null differ diff --git a/website/static/img/screen_admin_dark.jpg b/website/static/img/screen_admin_dark.jpg index 1b047b2a0e..def47e174c 100644 Binary files a/website/static/img/screen_admin_dark.jpg and b/website/static/img/screen_admin_dark.jpg differ diff --git a/website/static/img/screen_admin_light.jpg b/website/static/img/screen_admin_light.jpg index 472502b520..e7a51abeb4 100644 Binary files a/website/static/img/screen_admin_light.jpg and b/website/static/img/screen_admin_light.jpg differ diff --git a/website/static/img/screen_apps_dark.jpg b/website/static/img/screen_apps_dark.jpg index 9f7569860d..c6d59dd188 100644 Binary files a/website/static/img/screen_apps_dark.jpg and b/website/static/img/screen_apps_dark.jpg differ diff --git a/website/static/img/screen_apps_light.jpg b/website/static/img/screen_apps_light.jpg index dfa28df848..325e5660fe 100644 Binary files a/website/static/img/screen_apps_light.jpg and b/website/static/img/screen_apps_light.jpg differ diff --git a/website/static/img/screen_flow_dark.jpg b/website/static/img/screen_flow_dark.jpg index e1a4085a50..c38ed2ebe1 100644 Binary files a/website/static/img/screen_flow_dark.jpg and b/website/static/img/screen_flow_dark.jpg differ diff --git a/website/static/img/screen_flow_light.jpg b/website/static/img/screen_flow_light.jpg index 451f56e931..42b98a95cd 100644 Binary files a/website/static/img/screen_flow_light.jpg and b/website/static/img/screen_flow_light.jpg differ